高性能MySQL梳理总结

cover.png

一、基础

  1. 逻辑架构图


    逻辑架构.png
    1. 连接层:不是Mysql独有,连接处理、授权认证、安全等
    2. 服务层:核心服务,包括查询缓存、解析、分析、优化、执行,还包括所有跨存储引擎的功能:存储过程、触发器、视图等
    3. 引擎层:负责数据的存储与提取,不同的存储引擎都有各自的优劣势
    4. 所有子系统间共享的通用函数;基于线程资源的管理的体系结构;提高查询速度减少IO次数的缓存;维护全部会话与通讯的网络管理;管理所有数据、操作、慢查询、访问等日志的系统;数据文件与存储方式、格式、位置的管理系统;
  2. 事务
    事务是一组原子性的SQL查询,或者是一个独立的工作单元,事务内的语句,要么全部执行成功,要么全部失败

    1. 原子性(atomicity)
      不可分割的最小单元,要么全部提交、要么全部回滚
    2. 一致性(consisitency)
      总是从一个一致性的状态转换到另一个状态,
    3. 隔离性(isolation)
      在提交前,对其他事务不可见(不一定,受隔离级别影响)
    4. 持久性(durability)
      一旦提交,会永久保存到数据库中

    Mysql 事务默认是AutoCommit
    查看是否开启自动提交:SHOW VARIABLES LIKE 'AUTOCOMMIT'
    设置自动提交:SET AUTOCOMMMIT = 1/0

  3. 隔离级别
    对于多个并发执行的事务,在操作相同的数据时,可能发生数据不一致等问题,隔离级别可以避免这一问题

隔离级别(Isolation Level) 脏读(Dirty Read) 不可重复读(Non Repeatable Read) 幻读(Phantom Read)
未提交读(Read UnCommmitted)
提交读(Read Committed)
可重复读(Repeatable Read)
串行化(Seriallzable)
  1. Read UnCommitted
    可以读取其他事务未提交的数据
  2. Read Committed
    只能读取已经提交事务的修改,一个事务中,可能俩次读取的数据是不同的;大多数(不包括Mysql)数据库的默认隔离级别,
  3. Repeatable Read
    在一个事务中,多次读取相同数据是一致的,但是会出现幻读(在范围查询时,可能有新数据插入,导致读取的行数不一致),InnoDB & XtraDB存储引擎通过多版本控制来解决
  4. Serializable
    会在每行数据上都加锁,强制事务串行化,导致的问题:大量的超时、锁竞争、并发能力低,实际中很少用

设置隔离级别
全局:SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
当次会话有效:SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

  1. 日志
    • binLog (二进制日志)
      1. 通过追加的方式记录写入性操作日志,以而精致的方式保存在磁盘中,是mysql的逻辑日志,由Server层进行记录,使用任何引擎都会记录binlog日志
      2. 作用:主从复制、数据恢复
      3. 写入时机:事务提交时记录binlog,通过sync_binlog控制,0(不强制要求,由系统判断何时写入)、1(每次commit都写入binlog)、N(每N个事务,写入一次binlog)
      4. 日志格式:
        1. Statment(5.7.7前默认):基于SQL语句的复制,每一条修改都会记录到binlog
          优点:不需要记录每一行的变化,减少binlog,节约IO,提高性能
          缺点:有些情况会导致数据不一致,sysdate(),sleep()等、串行化执行
        2. Row(5.7.7默认):基于行的复制,记录那条数据被修改了
          优点:不会出现某些特定情况下的存储过程、function、的调用和触发无法被正确复制ide问题、不需要穿行执行,减少锁的使用
          缺点:会产生大量的日志,尤其是alter table;
        3. Mixed:Statment & Row的混合,一般使用Statment,不能的使用Row
    • redoLog(事务日志):
      1. 记录数据页的变更,保证事务一致性,提交事务时,将该事务涉及到的数据修改全部刷新到磁盘中
      2. 构成:主要包括俩部分:1. 日志缓存;2. 日志问价;每执行一条DML,先写入缓存,后续某个时间点,一次性将多个操作写到文件,这种先写缓存,后写文件的方式就是WAL技术;

对比:

redo log binlog
文件大小 固定 通过max_binlog_size 设置每个文件的大小
实现方式 InnoDB独有 Server层实现
记录方式 循环写的方式记录,写到结尾时,回到开头循环写 追加的方式
适用场景 适用于奔溃恢复(crash-safe) 使用于主从复制和数据恢复

binlog适用于归档,只依靠binlog是没有crash-safe能力的,同时只有redolog也是不行,是InnoDB独有,日志记录落盘后会被覆盖掉,同时保证服务宕机重启可以快速恢复,同时也是实现MVCC的实现关键;

  • undoLog(事务日志):
    提供回滚 & MVCC : insert -> delete
  • 慢查询日志:
    slow_query_log:默认是OFF,可以捕获执行时间超过阈值的SQL语句
    long_query_time: 执行时间的阈值,默认是10s,
    slow_query_log_file:日志文件名
    log_queries_not_using_indexes:捕获所有未使用索引的查询
  1. 多版本控制(MVCC---Multi-Version Concurrency control)

    1. 简介:基于锁的并发控制,通过版本号,避免同一数据在不同事务间的竞争(基于版本的乐观锁),在InnoDB存在,为了实现事务的隔离性,在Read Committed和Pereatable Read隔离级别生效,读不加锁,读写不冲突,提高并发性能
    2. 实现方式:乐观并发控制,悲观并发控制
    3. 实现原理:InnoDB在每行都有三个隐藏字段
      DB_ROW_ID(行号):六个字节,递增行ID,
      DB_TRX_ID(创建版本号):六个字节,最后一次修改本行记录的事务ID,
      DB_ROLL_PTR(回滚版本号):七个字节,回滚撤销日志的记录ID
      通过undoog & readview实现,俩种隔离级别的区别是生成的ReadView的时机不同,RC中,每次查询都会生成一个实时的ReadView,保证每次提交后的数据是处于当前的可见状态;RR中,当前事务第一次查询时生成当亲的ReadView,并且当前的ReadView会一直沿用到当前事务的提交,以此来保证可重复读;

  2. 锁保证数据并发访问的一致性、有效性的重要手段,MySQL服务器层,存储引擎层做并发控制,

    1. 共享锁:别的事务可读,但是不能写:select * from table_name where .... lock in share mode
    2. 排他锁:别的事务不可读、不可写: .............for update
    3. 行锁:开销大,加锁慢,会出现死锁,锁定粒度最小,发生冲突的概率小,并发度高,
    4. 表锁:开销小,加锁快,不会出现死锁,锁定粒度大,发生冲突概率大,并发能力低,
    5. 页面锁:开销间于行锁& 表锁间,会出现死锁,并发读一般
    6. 间隙锁:是InnoDB在RR级别下,为了解决幻读引入的锁机制,
    7. 乐观锁
    8. 悲观锁
  3. 磁盘存储结构 & 文件管理
    InnoDB尽可能的使用异步IO,降低I/O操作和优化磁盘文件,

    1. 预读(Read-Ahead)
      当InnoDB可以确定如果某些数据将可能被使用,则会执行预读操作,数据载入缓存池
    2. 双写缓存区
      文件刷新技术,提升当数据库奔溃或断点后恢复的安全性,对于大多数unix系统,通过减少fsync()调用来提升数据库性能,默认开启(innodb_doublewrite),在将页面写入连续的表空间区域(双写缓存区),只有在写并刷新双写缓存区后,InnoDB才会将页面写入数据文件中的正确位置,
    3. 页面、扩展区、段、表空间


      image.png
      • 段(Segment):包含数据段、索引段、回滚段等,,在InnoDB中,对段的管理是由引擎自身完成
      • 区(extent) :由连续的页组成,无论页的大小如何变,区的大小是固定的,默认个1MB,为了保证区中页的连续性,InnoDB一次从磁盘申请4 - 5个区,页的默认为16kb,一个区一共64(1024/16) 16个连续的页;每个段开始32页大小的碎片页来存储数据,使用完这些页后,再申请64个连续的页,目的是,对于一些小表或undo类的段,可以开始申请较小的空间,节省磁盘开销
      • 页/块(page):是InnoDB管理的最小单位,默认16KB(innodb_page_size),常见的页有数据页、undo页、系统页、事务数据页、插入缓存位图页、插入缓存空闲列表页、未压缩的二进制大对象页、压缩的二进制大对象页
      • 行(row):数据都是按行存放的,每页存放的行记录都是有硬性定义的,最多允许存放16KB/2-200(7992行)
  4. Page 页

    结构 & 大小 作用
    文件头(38byte) 文件头,描述页的信息
    页头(56byte) 页头,页的状态信息
    最大最小记录(26byte) 最小和最大记录,虚拟的行记录
    用户记录 用户记录,存储行记录内容
    空闲空间 页中没有被使用的空间
    页目录 用户记录的相对位置
    文件尾(8byte) 校验页是否完成
    1. 第一部分
      通用部分,主要指文件头 & 文件尾,将页的内容进行封装,通过头、尾校验CheckSum方式来确保页的传输完整,来个字段FIL_PAGE_PREV & FIL_PAGE_NEXT指向上一页、下一页,相当于双向列表,
    2. 第二部分
      记录部分,存储记录,最大、最小记录、用户记录占了页的主要空间,新记录插入时,将从空闲列表中分配空间,一页必须存储两行记录,否则就不是B+tree,而是链表
    3. 第三部分
      索引部分,这里主要是指页目录,起到记录索引的作用,在页中,记录是以单向链表的形式进行存储,缺点就是检索效率太低,在页目录中提供了二分查找的方式,将记录分为若干组,包括最大、最小记录,第一组:最小的一个记录;最后一组:最大的记录的分组,会有1-8条记录,其余的组记录数量在4-8条,页目录存储每组最后一条记录的地址偏移量,也被称为槽(slot)
      如果是通过B+tree的索引记录的,首先冲B+Tree树的根开始,逐层检索,直到找到叶子节点,也是找到数据页为止,将数据页加载到内存中,页目录的槽采用二分查找的方式粗略的找到记录分组,然后遍历链表
  5. 执行计划(Explain)
    Explain会返回每一步信息,是查看查询优化器如何决定执行查询的主要方法,这个功能有局限性,并不是总会显示真相,但是是最好的的信息,可以了解到底层是如何执行的,

    1. 相关列说明
      1. id:编号

      2. select_type:对应行是简单或复杂查询,
        SIMPLE:不包括子查询和UNION;
        PRIMARY:有复杂的查询,最外面标记为PRIMARY;
        SUBQUERY:包含在Select列表中的子查询中的Select(不在FORM子句中)
        DERIVED:用来表示包含在Form子句中的Select,mysql会递归执行并将结果放到一个临时表中(派生表),
        UNION:在Union中的第二个和税后的Select标记为Union,
        UNION RESULT:从Union的匿名临时表检索结果的Select

      3. table:访问的表名,或是别名

      4. type:访问类型
        all:全表扫描
        index:索引全表扫描,把索引从头到尾扫一遍,与全表扫描一样,只是按照索引次序扫描而不是行
        range:范围扫描,有限制的索引扫描,
        ref:索引访问,返回单值匹配的行,可能有多行,ref_or_null:二次查找,找出NULL条目
        index_merge:使用了俩个以上的索引,最后取交集或并集,常见and、or
        eq_ref:索引查找,最多返回一条记录,
        index_subquery:in形式的子查询,
        unique_subquery:where的in查询
        fulltext:全文索引搜索
        const、system:能对查询的某部分进行优化将其转化为一个常量

        顺序:system,const,eq_ref,ref,fulltext,ref_or_null,unique_subquery,index_subquery,range,index_merge,index,ALL,all不使用索引,index_merge多个索引

      5. possible_keys:查询可以使用哪些索引,可以高效的查找

      6. key:采用那个索引优化访问,可以最小化查询成本

      7. key_len:索引里使用的字节数,单列索引就是长度,多列是具体使用的和

      8. ref:在key列中查找值所用的列或常亮,如果使用的常数等值查询,显示const,

      9. rows:为了找到所需要行而要读取的行数

      10. filtered:5.1新加进去的,使用Explain Extended出现,针对表里符合某个条件的记录数的百分比所做的悲观估算,

      11. extra: 不适合在其他列显示的额外信息,
        Using index:使用覆盖索引,
        Using where:在存储引擎检索后在在进行过滤,
        Using temporary:在查询结果排序时,会使用一个临时表
        Using filesort:对结果使用外部索引排序,而不是按索引次序从表里读取行
        Range checked for each record : 没有好用的索引,新的索引将在链接的每一行上重新估算

二、优化

  1. 服务器
    相应时间是定义性能最有效的方法,=对系统进行新能剖析前,必须先进行测量,5.5提供了Performance Schema,基于时间的一些测量,

    1. 性能剖析
      排名、总结、平均值,性能剖析、定期检测都开会带来额外的开销,需要考虑开销与收益的比重
      • 哪些查询值得优化:占总相应时间比重小的查询不值得优化,无论怎么优化,效果都不明显;优化成本是否大于收益,如果是,应该立即停止优化
      • 异常情:有些异常情况会严重影响用户体验,但是总的响应时间占比并不突出
      • 未知的时间丢失:丢失的时间是总的任务时间与实际测量时间的差,这里需要引起抵消,可能存在性能问题
      • 被隐藏的细节,性能剖析无法显示所有的响应时间的分布,只相信平均值非常危险,会隐藏很多细节而无法表达全部情况,
    2. 哪些因素会导致性能瓶颈
      • 外部资源调用
      • 应用处理大量的数据
      • 循环中处理大量耗时操作,正则
      • 低效的算法
    3. 查询剖析
      1. 慢查询日志是开销最低、精度最高的测量查询时间的工具,在IO密集的场景,慢查询的开销日志鹿晗可以忽略不计,注意长期开启慢查询日志,要部署日志轮转日志(log rotation),第一种:通过--processlist选项,不断通过show full processlist输出,第二种:通过TCP抓包;
      2. 剖析语句:针对定位到的查询,获取更多的信息,
        • show status
          返回一些计数器,服务器级别的全局计数器、某个连接的会话级别的计数器,show global status,
        • show profile
          5.1后引入,默认是禁用的,set profiling = 1; 测量其消耗的时间和其他一些查询状态变更的相关数据,可能会被Performance Schema取代,
        • Performance Schema
        • show processlist:收集是否有线程处于不正常的状态,
  2. Schema & 数据类型

    1. 数据类型选择

      • 最小的数据类型:一般使用正确的最小的数据类型,因为更小的是占用磁盘更小、内存、CPU缓存,更快;同时需要考虑增加数据类型的范围是一个非常耗时和痛苦的操作
      • 简单的数据类型:简单的数据类型需要更少的CPU周期,例如:应该用内建的类型(日期而不是字符串)存储日期,整型而不是字符串存储IP
      • 尽量避免NULL:通常最好指定为NOT NULL,对于NULL列的优化更难,Null的索引、索引统计、值比较都更加复杂,null的会使用更多的空间,特殊处理

      首先确定合适的类型,然后是选择具体的类型(DateTime和Timestamp都是时间的,但是前者是后者存储空间的二倍,后者会根据时区变化

    2. 数据类型

      • 整数:TinyInt(8位)、SmallInt(16位)、MediumInt(24位)、Int(32位)、BigInt(64位),可选Unsigned有无符号,对于int(5)对于存储是没有意义的,只是规定一些交互工具显示的字符个数;
      • 实数: 有小数部分的数字,Decimal用于存储精确的小数,5.0后支持Decimal直接的计算,会将数字打包成二进制字符串中(每4个字节存9个数字)Decimal(18,9),9个字节,小数点占一个,允许的最多65个数字;早起的版本中只是一种存储格式,计算时会转为Double;因为需要Decimal要使用额外的空间和计算开销,所以在
      • 字符串:Varchar可变的,比定长节省空间(ROW_FORMAT=FIXED例外),需要使用1或2个字节记录字符串长度(长度是否255),节省空间对性能有帮助,但是在update时,如果比原来更长,会导致需要额外的工作,如果行占用的空间增长,并且在页内没有跟多的空间,不同的引擎处理方式不同,MyISAM会将行拆成不同的片段存储,InnoDB需要分裂页来使行可以放进页内;Varchar适用于最大长度比平均长度大很多,列的更新很少,所以碎片不是问题;Char是定长的,采用空格进行填充,适合存储很短的字符串,或者长度相近的,比如MD5,经常变动的字符串也比Varchar更好,char不容易产生碎片,
      • Text/Blob:存储更大的数据,分别采用字符、二进制方式存储,有TinyText、SmallText、Text、MediumText、LongText对应的二进制TinyBlob、SmallBlob、Blob、MediumBlob、LongBlob,Blob -> SmallBlob,Text -> SmallText,这些对象都有特殊的处理,在InnoDB中使用专门的*"外部"存储区域进行存储,需要1-4个字节存储一个指针,然后在外部存储实际的值,Blob(二进制、没有排序、没有字符集),Text(字符、有排序、有字符集),排序对max_sort_length个字节排序,或者使用Order by Sustring(column,length);
      • Enum:有时候可以使用枚举替代字符串,枚举非常紧凑,会根据列表的值数量压缩到一个或俩个字节,
      • 日期 & 时间:mysql能存储的最小的单位是秒,也可以使用微妙粒度进行临时运算,提供两种相似的Timestamp & DateTime,各有优势,DateTime:范围是1001 - 9999年,精度为秒,格式为:YYYYMMDDHHMMSS,与时区无关,8个字节存储,TimeStamp:从1970- 2038年,精度为毫秒,使用四个字节存储,与时区有关;后者比前者空间效率更高,
      • 位数据:Bit:最长64位,MyIsam打包所有的Bit列,当做字符串类型,而不是数字类型,Set:保存多个true/false,可以合并到一个Set数据类型中,缺点是改变列的代价较高
      • 标识符:MyIsam默认对字符串使用压缩索引,导致查询慢的多,会有6倍的性能下降,对于随机的字符串,新值会任意分布在很大的空间,会导致Insert以及一些Select会变慢,因为插入会随机写到索引的不同的位置,会导致列分裂、磁盘随机访问,以及对于聚簇存储产生碎片,select会慢,因为逻辑相邻的行会分布在磁盘和内存的不同的地方
    3. Schema设计

      • 太多的列:存储引擎在服务器层、存储引擎层之间通过行缓存格式数据拷贝,然后服务器层将缓存内容解码,这个这个过程代价是非常高的
      • 太多的关联:实体 -> 属性 (EAV)设计模式是常见的非常糟糕的模式, Myslq限制最多关联61张表,
      • 枚举:注意防止过多使用枚举,
      • Null:注意Null的合理使用
    4. 范式 & 反范式:通过遵守范式,可以设计关系型数据库,各种范式呈递次规范,越高的范式数据库冗余越小,些时候一昧的追求范式减少冗余,反而会降低数据读写的效率,这个时候就要反范式,利用空间来换时间。

      • 第一范式:即表的列的具有原子性,不可再分解,即列的信息,不能分解;
      • 第二范式:在第一范式的基础上建立起来的,满足第二范式必须先满足第一范式。第二范式要求数据库表中的每个实例或行必须可以被惟一地区分。为实现区分通常需要我们设计一个主键来实现(这里的主键不包含业务逻辑)
      • 第三范式:满足第三范式必须先满足第二范式。简而言之,第三范式要求一个数据库表中不包含已在其它表中已包含的非主键字段。就是说,表的信息,如果能够被推导出来,就不应该单独的设计一个字段来存放(能尽量外键join就用外键join)。很多时候,我们为了满足第三范式往往会把一张表分成多张表
      • 反范式:没有冗余的数据库未必是最好的数据库,有时为了提高运行效率,就必须降低范式标准,适当保留冗余数据。具体做法是: 在概念数据模型设计时遵守第三范式,降低范式标准的工作放到物理数据模型设计时考虑。降低范式就是增加字段,减少了查询时的关联,提高查询效率,因为在数据库的操作中查询的比例要远远大于DML的比例。但是反范式化一定要适度,并且在原本已满足三范式的基础上再做调整的。
  3. 索引
    是存储引擎用于快速找到记录的一种数据结构

    1. 优点
      索引大大减小了服务器需要扫描的数据量
      索引可以帮助服务器避免排序和临时表
      索引可以将随机IO变成顺序IO
      索引对于InnoDB(对索引支持行级锁)非常重要,因为它可以让查询锁更少的元组。在MySQL5.1和更新的版本中,InnoDB可以在服务器端过滤掉行后就释放锁,但在早期的MySQL版本中,InnoDB直到事务提交时才会解锁。对不需要的元组的加锁,会增加锁的开销,降低并发性。 InnoDB仅对需要访问的元组加锁,而索引能够减少InnoDB访问的元组数。但是只有在存储引擎层过滤掉那些不需要的数据才能达到这种目的。一旦索引不允许InnoDB那样做(即索引达不到过滤的目的),MySQL服务器只能对InnoDB返回的数据进行WHERE操作,此时,已经无法避免对那些元组加锁了。如果查询不能使用索引,MySQL会进行全表扫描,并锁住每一个元组,不管是否真正需要。
      关于InnoDB、索引和锁:InnoDB在二级索引上使用共享锁(读锁),但访问主键索引需要排他锁(写锁)

    2. 缺点
      虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存索引文件。
      建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果你在一个大表上创建了多种组合索引,索引文件的会膨胀很快。
      如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。
      对于非常小的表,大部分情况下简单的全表扫描更高效;
      索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句。
      因此应该只为最经常查询和最经常排序的数据列建立索引。
      MySQL里同一个数据表里的索引总数限制为16个。

    3. 索引类型:

      • B-Tree索引:InnoDB使用的是B+Tree。
      • B+Tree:每一个叶子节点都包含指向下一个叶子节点的指针,从而方便叶子节点的范围遍历。

      B-Tree通常意味着所有的值都是按顺序存储的,并且每一个叶子页到根的距离相同,很适合查找范围数据。
      B-Tree可以对<,<=,=,>,>=,BETWEEN,IN,以及不以通配符开始的LIKE使用索引。

    4. 匹配规则:全指匹配、最左前缀、匹配列前缀、匹配范围值、精确匹配一列范围匹配另一列

    5. 哈希索引:基于哈希表实现,自由精确匹配索引所有的列查询才有效,哈希索引只包含哈希值和行指针,而不存储字段值,所以避免不了读取行;不是按照顺序存储,无法排序;不支持部分索引匹配查找,支持支等值比较;访问速度快,除非哈希冲突多,

    6. 自适应哈希索引:InnoDB中某些索引值被频繁使用时,会在内存中基于B-Tree索引上再创建一个哈希索引,

    7. 全文索引:查找全文中的关键词

    8. 聚簇索引:是一种数据存储方式,一个表只能有一个聚簇索引。将数据存储与索引放到了一块,找到索引也就找到了数据,数据的物理存放顺序与索引顺序是一致的

    9. 非聚簇索引:将数据存储于索引分开结构,索引结构的叶子节点指向了数据的对应行,MyIsam通过key_buffer把索引先缓存到内存中,当需要访问数据时(通过索引访问数据),在内存中直接搜索索引,然后通过索引找到磁盘相应数据,这也就是为什么索引不在key buffer命中时,速度慢的原因

  4. 查询

    1. Mysql优化和执行查询过程
      061033331994784.png
    • 客户端发送查询到服务端
    • 服务器先检查查询缓存,如果命中,返回
    • 服务器进行Sql解析、预处理、再由优化器生成对应的执行计划
    • 根据执行计划,调用存储引擎的API来执行查询
    • 将结果返回给客户端
    1. 查询慢的原因
      1. 检索大量超过需要的数据,比如不需要的数据、全部列
      2. 确认服务器层,是否分析大量超过需要的数据行,扫描的行数、返回的行数
    2. 重构查询方式
      1. 将一个复杂查询拆成多个简单的查询
      2. 切分查询
      3. 分解关联查询
  1. 操作系统 & 硬件
    在性能与成本之间找到一个平衡点,
    1. CPU:如果在更快 & 更多做选择,一般会选择更快
    2. IO:顺序IO与随机IO、闪存
    3. 硬盘:固态 & 机械
    4. 网络:网络一般不会作为很严重你的瓶颈
  2. 应用层优化
    不要局限于Mysql本身性能而花费太多时间,或许MySql已经足够优化,应该关注下其他部分,
    1. 常见问题
      • 消耗系统资源的根本原因
      • 是否获取了冗余数据
      • 应用层 & DB层是否是各司其职,是否使用合理
      • 执行了太多的查询,ORM导致的查询,可以考虑在DB层做关联
      • 创建了没必要的连接,使用连接池
    2. Web服务器问题
      • 使用缓存代理服务器
      • 对动态 & 静态资源设置过期策略
      • 寻找最优的并发度
    3. 缓存
      缓存多高负载应用来说至关重要,要找到正确的粒度和缓存过期策略
      • 本地缓存:很小,在进程处理期间在内存中
      • 本地共享内存缓存:中等大小,快速、难以在多台机器间同步
      • 分布式内存缓存:例如Memcached,需要考虑一致性问题
      • 磁盘缓存:很慢,最好是持久化对象
        失效策略有TTL(设置过期时间)、显式失效(数据更新时失效)、读时失效(根据一些信息,作为是失效标记),缓存对象得分层、缓存预热

三、如何达到高性能

  1. 复制
    复制是构建大规模、高性能应用的基础,提供高可用、可扩展、容灾恢复的能力
    1. 解决了哪些问题:服务器间保持数据同步,形成主备,主要解决数据分布、负载均衡、备份、个高可用、故障切换、升级测试

    2. 方式:基于行的复制、基于语句的复制,都是通过在主库上记录二进制日志,在备库重放日志的方式来实现异步数据的复制,这意味着,在一个时间点,主备数据可能存在不一致,并无法保证延迟,复制主库主要是启用二进制日志带来的开销,每个备库也会对主库增加一些负载,随着备库的增加,主库的负载会累加

    3. 复制的流程:


      image.png

      在提交事务完成数据更新前,主库将变更记录写入到二进制日志,备库复制日志到中继日志并重放,主库一个线程,备库两个线程

    4. 原理

      1. 基于语句复制(逻辑复制)
        5.0及以前只支持该复制方式,主库会记录所有的变更语句,在备库将变更语句执行一遍,好处就是简单,不会使用太多带宽,但实际存在一些问题,例如元数据信息,无法被真确复制的SQL、CURRENT_USER等,同时更新必须是穿行的,需要更多的锁,
      2. 基于行的复制
        5.1开始,将实际数据记录在二进制日志中,最大的好处就是可以正确的复制每一行,由于无需重放查询,所以更高效,但是在一些表变更中,需要全表复制变更,所以开销会很大
      优点 缺点
      基于语句 在主备不同模式时,可以在多种情况下运行,原理简单,定位问题容易 很多情况下无法正确复制,如果使用触发器、存储过程,就不要使用该方式
      基于行 没有行复制不能处理的问题,能更快的解决数据不一致的情况。 没有记录语句,所以无法判断执行了哪些语句,在某些情况下,修改了Schema、没有找到行等会导致异常
      1. 事件 & 过滤
        • 可以通过log_slave_updates让备库称为其他服务器的主库
        • 可以设置,只复制一部分数据,有俩种过滤方式,1. 在主库过滤记录到二进制日志中的日志事件,2. 备库过滤记录到中继日志事件,


          image.png
    5. 拓扑
      可以在任意多个主备间复制,唯一限制:一个备库只能有一个主库,

      1. 一主多备
        简单灵活,能满足多种需求
        • 不同的角色使用不同的备库
        • 将一台备库当做待用主库、用作容灾恢复
        • 可以使用一个备库做测试
      2. 双主主动复制
        每一个被配置成对方的主库和备库,在不同的位置,都需要可写,最大的问题就是如何解决冲突,
      3. 双主被动复制
        双主的衍生,区别是其中一台是只读的被动服务器,可以有效的避免数据冲突问题,在反复切换主动、被动服务器很方便,
      4. 双主多备
        每个主库有相应的备库,消除单点
      5. 环形复制
        非常脆弱、尽量避免
      6. 主、分发主、备
        在备库越来越多的情况下会对主库造成很大的负载,每个备库会在主库创建一个binlog dump命令,如果不在缓存,会导致大量的磁盘IO,这种情况下,可以使用分发主库减轻主库的负担,分发主库其实就是一个备库,负责提取和提供主库的二进制日志
      7. 数形结构


        image.png

        不管是分发,还是提供更高的读性能,都是更好的管理,缺点是中间层出现问题会影响后面的服务器,
        过大的复制延迟是一个很普遍的问题,最好在设计应用程序时能够让其容忍备库出现延迟,如果无法容忍,那就不要使用复制,单线程的设计,导致了备库的效率低下,备库的锁同样是问题,会阻塞复制线程

    6. 复制总结

逻辑上 技术上
异步复制 默认的复制方式,主库在执行完客户端提交的事务后会立即将结果返给给客户端,并不关心从库是否已经接收并处理,这样就会有一个问题,主如果crash掉了,此时主上已经提交的事务可能并没有传到从库上,如果此时,强行将从提升为主,可能导致新主上的数据不完整。 主库将事务 Binlog 事件写入到 Binlog 文件中,此时主库只会通知一下 Dump 线程发送这些新的 Binlog,然后主库就会继续处理提交操作,而此时不会保证这些 Binlog 传到任何一个从库节点上。
全同步 当主库执行完一个事务,所有的从库都执行了该事务才返回给客户端。因为需要等待所有从库执行完该事务才能返回,所以全同步复制的性能必然会收到严重的影响。 当主库提交事务之后,所有的从库节点必须收到、APPLY并且提交这些事务,然后主库线程才能继续做后续操作。但缺点是,主库完成一个事务的时间会被拉长,性能降低。
半同步 介于全同步复制与全异步复制之间的一种,主库只需要等待至少一个从库节点收到并且 Flush Binlog 到 Relay Log 文件即可,主库不需要等待所有从库给主库反馈。同时,这里只是一个收到的反馈,而不是已经完全完成并且提交的反馈,如此,节省了很多时间。 介于异步复制和全同步复制之间,主库在执行完客户端提交的事务后不是立刻返回给客户端,而是等待至少一个从库接收到并写到relay log中才返回给客户端。相对于异步复制,半同步复制提高了数据的安全性,同时它也造成了一定程度的延迟,这个延迟最少是一个TCP/IP往返的时间。所以,半同步复制最好在低延时的网络中使用。
  1. 可扩展
    当需要增加资源以处理更大的负载时,系统能够获得划算的等同提升的能力,以及投资产出率,缺乏扩展能力的系统,在达到一个点后,将无法增长;简单说,扩展性就是能够通过增加资源来提升处理负载的能力,负载要从数据量、用户量、用户活跃度、相关数据集的大小。
    大部分系统只能以线性扩展略低的扩展系数进行扩展,事实上,多数系统在达到一个最大吞吐量临界点,超过这个临界点,投入产出会带来负回报,

    1. 垂直扩展
      对于垂直扩展,意味着购买更多性能强悍的硬件,对很多应用来说这是唯一需要做的事情。如果在此基础上继续提升硬件的配置,MySQL的性能虽然还能提升,但性价比就会降低。
    2. 水平扩展
      一般策略划分为三个部分:复制、拆分、以及数据分片(sharding)。
      最简单也最常见的水平扩展(向外扩展)的方法是通过复制将数据分发到多个服务器上,然后将备库用于读查询。这种技术对于以读为主的应用很有效。他也有一些缺点,例如重复缓存,但如果数据规模有限就不存在这个问题。
      • 按功能拆分:将不同的功能或者是不同的业务尽可能地拆分开,然后把各个功能或业务需要的数据库独立运行
      • 数据分片:在目前用于扩展大型MySQL应用的方案中,数据分片是最通用且最成功的方法。他把数据分割成一小片,或者说一块,然后存储到不同的节点中。如果想扩展写容量,就必须切分数据。如果只有单台主库,那么不管有多少备库,写容量都是无法扩展的。最大的挑战是查找和获取数据,选择分区键非常重要,选择分区键的时候,尽可能选择那些能够避免跨分片查询的,但同时也要让分片足够小,以避免过大的数据片导致问题。如果可能,应该期望分片尽可能同样小,这样在为不同数量的分片进行分组时能够很容易平衡。
      • 分配方式有俩种:固定分配:固定分配使用的分区函数仅仅依赖于分区键的值,例如取模,优点是:简单、开销低、可以直接使用硬编码,缺点是如果分片很大并且数量不多,就很难平衡不同分片间的负载。修改分片策略比较困难,因为需要重新分配已有的数据。动态分配:将每个数据单元映射到一个分片,动态分配增加了分区函数的开销,因为需要额外调用一次外部资源,例如目录服务器(存储映射关系的数据存储节点)。动态分配的最大好处是可以对数据存储位置做细粒度的控制
      • 负载均衡:达到可扩展、高效、可用、透明、一致目的,读写分离,DNS、转移IP地址,引入中间件,负载均衡算法有随机、轮询、最少连接数、最快响应、哈希、权重
  2. 高可用
    更少的宕机时间,可用性每提高一点,所花费的成本都会远超从前,效果与开销不成线性

    1. 导致不可用的原因
      • 运营环境中,最普遍的问题是磁盘空间耗尽
      • 性能问题中,最普遍的是SQL的性能问题,
      • 糟糕的Schema和索引
      • 复制导致数据不一致问题
      • 数据丢失一般是Drop table导致
    2. 实现高可用
      首先减少宕机时间,第二宕机后快熟恢复,两个维度来确定:平均失效时间、平均恢复时间,
      • 提升平均失效时间
        测试回复工具和流程
        最小权限
        用好的命名和组织约定避免混乱,比如测试开发库分离
        确认服务器配置项
        通过skip_name_resolve禁止DNS
        除非证明有效,否则不使用查询缓存。
        避免使用复杂的特性,如触发器等
        监控重要组件
        尽量记录服务器状态和性能做成曲线查看走向
        定期检查复制完整性
        备库设置为只读,不要让复制自动启动
        对查询语句做检查
        归档并清理不需要的数据
        为文件系统保留空余空间,在linux中可以使用-m选项为系统本身保留空间。或者创建很大的空文件,在快满是删除
        养成习惯,评估和管理系统的改变,状态以及性能信息。
      • 降低平均恢复时间
        通过建立冗余来避免系统完全失效,比如避免单点。
        能够提供冗余和故障转移能力的系统架构
        熟悉业务的员工和详细的文档和流程
        宕机事后反思
    3. 避免单点
      单个磁盘,单台服务器,单台交换机路由器,单电力网。 任何不冗余的部分都可能是一个失败的单点。用池加负载均衡,可以动态的切换及避免冗余,共享存储或者复制,做到数据的冗余。共享存储就是在华为的时候的存储产品
    4. 同步复制
      mysql 普通的复制,主服务器挂掉之后会丢数据,使用同步复制保证至少在一条备机持久化之后才能持久化本地,所以减少了丢失。
    5. 故障转移和恢复
      冗余只是一个基础,真正影响可用性的是如何利用这些冗余做转移和恢复。转移是A出问题了切到B上,等A好了再切回去,这要比仅仅恢复A要好。
      通常有如下的手段来做:
      提升备库。
      使用虚拟IP,如果服务器出问题,则让虚拟ip指向备库。但是这样可能会因为网络缓存,ip接管等引起错误
      中间件 可以使用代理,端口转发,LVS等等方法来作为中间件,屏蔽应用和数据库。比如最简单的http代理

四、总结

首先通过对Mysql基础知识的大致串联,让我对Mysql具备的能力事务、日志、锁等有了基本的认识,然后在设计过程中,从服务器、Schema、索引、查询、应用等层如何让系统应用达到最优,最后通过复制、可扩展、高可用让Mysql性能达到最优,通过上述介绍,让我们在实际使用中,可以更能深入了解Mysql的运行原理,从而写出高性能的Sql以及遇到问题可以快速定位优化

参考:
《高性能MySql》第三版
官网:https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html
日志:https://www.cnblogs.com/f-ck-need-u/archive/2018/05/08/9010872.html
MVCC:https://blog.csdn.net/qq_35190492/article/details/109044141
锁:https://zhuanlan.zhihu.com/p/29150809
磁盘IO:https://t.hao0.me/mysql/2016/10/25/mysql-innodb-04-io-file-mgr.html
分区:https://database.51cto.com/art/202003/612742.htm
页结构:https://blog.haohtml.com/archives/19232

`

你可能感兴趣的:(高性能MySQL梳理总结)