文章目录

  • 介绍
  • 第1章 MySQL架构介绍
    • 1.1 MySQL简介
    • 1.2 MySQL主流的分支版本
    • 1.3 MySQL存储引擎
    • 1.4 MySQL逻辑架构
    • 1.5 MySQL物理文件体系结构
  • 第2章 InnoDB存储引擎体系结构
    • 2.1 缓冲池
    • 2.2 change buffer
    • 2.3 自适应哈希索引
    • 2.4 redo log buffer
    • 2.5 double write
    • 2.6 InnoDB后台线程
      • 2.6.1 InnoDB主线程
      • 2.6.2 InnoDB后台I/O线程
      • 2.6.3 InnoDB脏页刷新线程
      • 2.6.4 InnoDB purge线程
    • 2.7 redo log
    • 2.8 undo log
    • 2.9 Query Cache
  • 第3章 MySQL事务和锁【*】
    • 3.1 MySQL事务概述
    • 3.2 MySQL事务隔离级别
    • 3.3 InnoDB的锁机制介绍
    • 3.4 锁等待和死锁
      • 3.4.1 锁等待
      • 3.4.2 死锁
    • 3.5 锁问题的监控
  • 第4章 SQL语句性能优化【*】
    • 4.1 MySQL查询过程
    • 4.2 创建高性能索引【*】
      • 4.2.1 索引的原理
      • 4.2.2 聚集索引和辅助索引
      • 4.2.3 Index Condition Pushdown(索引下推)【*】
        • 实验举例
      • 4.2.4 Multi-Range Read Optimization
      • 4.2.5 Batched Key Access
    • 4.3 慢SQL语句优化思路
      • 4.3.1 抓取慢SQL语句
      • 4.3.2 利用explain分析查询语句【*】
      • 4.3.3 利用show profiles分析慢SQL语句
    • 4.4 索引使用的原则及案例分析【*】
      • 4.4.1 索引使用的原则
      • 4.4.2 没有使用到索引的案例分析
  • 第5章 MySQL服务器全面优化
  • 第6章 MySQL性能监控
  • 第7章 MySQL主从复制详解【*】
    • 7.1 主从复制的概念和用途
    • 7.2 主从复制的原理及过程描述
    • 7.3 主从复制的重点参数解析
    • 7.4 主从复制的部署架构
      • 7.4.1 一主一从或一主多从
      • 7.4.2 多级主从(级联同步)
      • 7.4.3 双主
      • 7.4.4 多主一从(也称多源复制,MySQL 5.7之后开始支持)
    • 7.5 异步复制
      • 7.5.1 搭建主从复制必要条件
      • 7.5.2 主从复制具体搭建过程
    • 7.6 半同步复制
      • 7.6.1 半同步复制概念和原理
      • 7.6.2 半同步复制配置
    • 7.7 GTID复制
      • 7.7.1 GTID特性和复制原理介绍
      • 7.7.2 GTID复制配置实战
    • 7.8 多源复制
    • 7.9 主从复制故障处理
      • 7.9.1 主从复制在生产环境中常见的故障
      • 7.9.2 主从复制的数据一致性检查
    • 7.10 主从延迟解决方案和并行复制
      • 7.10.1 主从延时排查方法
      • 7.10.2 延迟的优化解决方法
      • 7.10.3 MySQL 5.7并行复制Multi-Threaded Slave原理(简称MTS)
      • 7.10.4 MySQL 5.7并行复制配置
  • 第8章 PXC高可用解决方案
  • 第9章 基于MHA实现的MySQL自动故障转移集群
  • 第10章 MySQL Group Replication
  • 第11章 Keepalived+双主复制的高可用架构
  • 第12章 数据库分库分表与中间件介绍【*】
    • 12.1 关系数据库的架构演变
      • 12.1.1 数据库读写分离(解决访问压力)
      • 12.1.2 数据库垂直分库(解决数据库量大)
      • 12.1.3 数据库水平分库与水平分表(解决单表数据量大)
    • 12.2 分库分表带来的影响
      • 12.2.1.分布式事务问题
      • 12.2.2.跨库join的问题
      • 12.2.3.结果集合并、排序的问题
      • 12.2.4.数据迁移、扩容问题
    • 12.3 常见的分库分表中间件介绍
  • 第13章 Mycat中间件详解(核心:数据库分片)
    • 13.1 Mycat简介
    • 13.2 Mycat核心概念
    • 13.3 Mycat安装部署
    • 13.4 Mycat配置文件详解
      • 13.4.1 schema.xml
      • 13.4.2 server.xml
      • 13.4.3 rule.xml
    • 13.5 Mycat分库分表实战【*】
    • 13.6 Mycat读写分离实战【*】

介绍

本篇内容摘自《MySQL性能优化和高可用架构实践-宋立桓-清华大学出版社》

主要包括书中个人认为的重点部分。

第1章 MySQL架构介绍

1.1 MySQL简介

1.2 MySQL主流的分支版本

1.3 MySQL存储引擎

​ 介绍MySQL中三种存储引擎:有InnoDB、MyISAM、Memory。简单地理解,存储引擎就是指表的类型以及表在计算机上的存储方式。存储引擎的概念是MySQL的特色,使用的是一个可插拔存储引擎架构,能够在运行的时候动态加载或者卸载这些存储引擎。不同的存储引擎决定了MySQL数据库中的表可以用不同的方式来存储。我们可以根据数据的特点来选择不同的存储引擎。

​ 在MySQL中的存储引擎有很多种,可以通过SHOW ENGINES语句来查看,如图1-3所示。

​ 在Support列中,YES表示当前版本支持这个存储引擎;DEFAULT表示该引擎是默认的引擎,即InnoDB。

​ 下面重点关注InnoDB、MyISAM、MEMORY这3种:

  • (1)InnoDB存储引擎

    • ① InnoDB是事务型数据库的首选引擎,支持事务ACID,简单地说就是支持事务完整性、一致性。
    • ② InnoDB支持行级锁。行级锁可以在最大程度上支持并发,以及类似Oracle的一致性读、多用户并发。
    • ③ InnoDB是为处理巨大数据量的最大性能设计,InnoDB存储引擎完全与MySQL服务器整合,InnoDB存储引擎为在主内存中缓存数据和索引而维持它自己的缓冲池。
    • ④ InnoDB支持外键完整性约束,存储表中的数据时,每张表的存储都按照主键顺序存放,如果没有显式在表定义时指定主键,InnoDB会为每一行生成一个6字节的ROWID,并以此作为主键。
    • ⑤ InnoDB支持崩溃数据自修复。InnoDB存储引擎中就是依靠redo log来保证的。当数据库异常崩溃后,数据库重新启动时会根据redo log进行数据恢复,保证数据库恢复到崩溃前的状态。
  • (2)MyISAM存储引擎

    • MyISAM存储引擎不支持事务,所以对事务有要求的业务场景不能使用。
    • 其锁定机制是表级索引,虽然可以让锁定的实现成本很小,但是也同时大大降低了其并发性能。
    • ③ 不仅会在写入的时候阻塞读取,MyISAM还会在读取的时候阻塞写入,但读本身并不会阻塞另外的读。
    • ④ 只会缓存索引:MyISAM可以通过key_buffer缓存,以大大提高访问性能减少磁盘I/O,但是这个缓冲区只会缓存索引,而不会缓存数据
    • ⑤ 适用于不需要事务支持(不支持)、并发相对较低(锁定机制问题)、数据修改相对较少(阻塞问题)、以读为主这类场景。
  • (3)MEMORY存储引擎

    • MEMORY存储引擎是MySQL中的一类特殊存储引擎,使用存储在内存中的内容来创建表,而且所有数据也放在内存中。
    • ① 每个基于MEMORY存储引擎的表实际对应一个磁盘文件。该文件的文件名与表名相同,类型为frm类型。该文件中只存储表的结构,数据文件则存储在内存中。
    • ② MEMORY默认使用哈希索引,速度比使用B型树索引快。如果想用B型树索引,可以在创建索引时指定。
    • ③ MEMORY存储引擎是把数据存到内存中,如果内存出现异常就会影响数据。如果重启或者关机,那么所有数据都会消失。

    ​ 在实际工作中,选择一个合适的存储引擎是比较复杂的问题。每种存储引擎都有自己的优缺点,不能笼统地说谁比谁好。如果需要对事务的完整性要求比较高(比如银行),要求实现并发控制(比如售票),那么选择InnoDB有很大的优势。如果表主要是用于插入记录和读出记录,那么选择MyISAM能实现处理高效率。如果需要很快的读写速度,对数据的安全性要求较低,可以选择MEMORY,它对表的大小有要求,不能建立太大的表。

1.4 MySQL逻辑架构

​ MySQL数据库的逻辑架构简单的图示如图1-4所示。

​ MySQL逻辑架构整体分为3层:

  • 第一层是客户端层,所包含的并不是MySQL独有的技术,它们都是服务于C/S程序或者是这些程序所需要的,诸如连接处理、身份验证、安全性等功能均在这一层处理。

  • 第二层是SQL层(SQL Layer),因为这是MySQL的核心部分,通常也叫作核心服务层。在MySQL数据库系统处理底层数据之前的所有工作都是在这一层完成的,包括权限判断、SQL解析、执行计划优化、Query cache的处理以及所有内置的函数(如日前时间、加密等函数)、存储过程、视图、触发器等。

  • 第三层是存储引擎层(Storage Engine Layer),是底层数据存取操作实现的部分,由多种存储引擎共同组成。它们负责存储和获取所有存储在MySQL中的数据,类似Linux的众多文件系统。每个存储引擎都有自己的优势和劣势,通过存储引擎API来与它们交互,这些API接口隐藏了各个存储引擎不同的地方。对于查询层尽可能透明。

1.5 MySQL物理文件体系结构

​ 简单理解就好吧(我感觉),补全介绍,日志文件很重要(主从复制就是依赖binlog二进制日志文件)

​ MySQL在Linux系统中的数据文件目录如图1-6所示,可以归结为如下几类。

(1)binlog二进制日志文件:不管使用的是哪一种存储引擎,都会产生binlog。如果开启了binlog二进制日志,就会有若干个二进制日 志 文 件 , 如 mysql-bin.000001 、 mysql-bin.000002 、 mysqlbin.00003等。binlog记录了MySQL对数据库执行更改的所有操作。查看当前binlog文件列表,如图1-7所示。

​ 在MySQL 5.1之前,所有的binlog都是基于SQL语句级别的。应用这种格式的binlog进行数据恢复时,如果SQL语句带有rand或uuid等函数,可能导致恢复出来的数据与原始数据不一致。从MySQL 5.1版本开始,MySQL引入了binlog_format参数。这个参数有可选值statement和row:statement就是之前的格式,基于SQL语句来记录;row记录的则是行的更改情况,可以避免之前提到的数据不一致的问题。做MySQL主从复制,statement格式的binlog可能会导致主备不一致,所以要使用row格式。我们还需要借助mysqlbinlog工具来解析和查看binlog中的
内容。如果需要用binlog来恢复数据,标准做法是用mysqlbinlog工具把binlog中的内容解析出来,然后把解析结果整个发给MySQL执行。

(2)redo重做日志文件:ib_logfile0、ib_logfile1是InnoDB引擎特有的、用于记录InnoDB引擎下事务的日志,它记录每页更改的物理情况。首先要搞明白的是已经有binlog了为什么还需要redo log,因为两者分工不同。binlog主要用来做数据归档,但是并不具备崩溃恢复的能力,也就是说如果系统突然崩溃,重启后可能会有部分数据丢失。Innodb将所有对页面的修改操作写入一个专门的文件,并在数据库启动时从此文件进行恢复操作,这个文件就是redo log file。redo log的存在可以完美解决这个问题。默认情况下,每个InnoDB引擎至少有一个重做日志组,每组下至少有两个重做日志文件,例如前面提到的ib_logfile0和ib_logfile1。重做日志组中的每个日志文件大小一致且循环写入,也就是说先写iblogfile0,写满了之后写iblogfile1 , 一 旦 iblogfile1 写 满 了 , 就 继 续 写 iblogfile0 。 当innodb log设置过大的时候,可能会导致系统崩溃后恢复需要很长的时间;当innodb log设置过小的时候,在一个事务产生大量日志的情况下,需要多次切换重做日志文件。

(3)共享表空间和独立表空间:在MySQL 5.6.6之前的版本中,InnoDB默认会将所有的数据库InnoDB引擎的表数据存储在一个共享表空间ibdata1中,这样管理起来很困难,增删数据库的时候,ibdata1文件不会自动收缩,单个数据库的备份也将成为问题。为了优化上述问 题 , 在 MySQL 5.6.6 之 后 的 版 本 中 , 独 立 表 空 间
innodb_file_per_table参数默认开启,每个数据库的每个表都有自已独立的表空间,每个表的数据和索引都会存在自己的表空间中。即便是innnodb表指定为独立表空间,用户自定义数据库中的某些元数据信息、回滚(undo)信息、插入缓冲(change buffer)、二次写缓冲(double write buffer)等还是存放在共享表空间,所以又称为系统表空间。

​ 这个系统表空间由一个或多个数据文件组成,默认情况下其包含一个叫ibdata1的系统数据文件,位于MySQL数据目录(datadir)下。系 统 表 空 间 数 据 文 件 的 位 置 、 大 小 和 数 目 由 参 数innodb_data_home_dir和innodb_data_file_path启动选项控制。当开启独立表空间参数innodb_file_per_table选项时,该表创建于自己的数据文件中,而非创建于系统表空间中。这样的好处在于在drop或者truncate时空间可以被收回至操作系统用作其他用途,而且进行单表空间在不同实例间移动,而不必处理整个数据库表空间。

​ 如图1-8所示,参数innodb_file_per_table独立表空间选项开启,同时通过“show variables like ‘innodb_data%’;”命令可以查询共享表空间(系统表空间)的文件信息。

(4)undo log:undo log是回滚日志,如果事务回滚,则需要依赖undo日志进行回滚操作。MySQL在进行事务操作的同时,会记录事务性操作修改数据之前的信息,就是undo日志,确保可以回滚到事务发生之前的状态。innodb的undo log存放在ibdata1共享表空间中,当开启事务后,事务所使用的undo log会存放在ibdata1中,即使这个事务被关闭,undo log也会一直占用空间。为了避免ibdata1共享表空间暴涨 , 建 议 将 undo log 单 独 存 放 。 如 图 1-9 所 示 ,innodb_undo_directory参数指定单独存放undo表空间的目录,该参数实例初始化之后不可直接改动(可以通过先停库,修改配置文件,然后 移 动 undo 表 空 间 文 件 的 方 式 去 修 改 该 参 数 ) ;innodb_undo_tablespaces参数指定单独存放的undo表空间个数,推荐设置为大于等于3;innodb_undo_logs参数指定回滚段的个数,默认为128个。MySQL 5.7引入了新的参数innodb_undo_log_truncate,这个参数开启后可在线收缩拆分出来的undo表空间。

(5)临时表空间:存储临时对象的空间,比如临时表对象等。如图1-10所示,参数innodb_temp_data_file_path可以看到临时表空间的信息,上限设置为5GB。

​ MySQL 5.7对于InnoDB存储引擎的临时表空间做了优化。在MySQL5.7之前,InnoDB引擎的临时表都保存在ibdata里面,而ibdata的贪婪式磁盘占用导致临时表的创建与删除对其他正常表产生非常大的性能影响。

​ 在MySQL 5.7中,对于临时表做了下面两个重要方面的优化:MySQL 5.7把临时表的数据从共享表空间里面剥离出来,形成自己单独的表空间,参数为innodb_temp_data_file_path;MySQL 5.7中把临时表 的 相 关 检 索 信 息 保 存 在 系 统 信 息 表 中 :information_schema.innodb_temp_table_info。在MySQL 5.7之前的版本中,想要查看临时表的系统信息没有太好的办法。

​ 虽然InnoDB临时表有自己的表空间,但是目前还不能自己定义临时表空间文件的保存路径,只能是继承innodb_data_home_dir。此时如果想要拿其他的磁盘(比如内存盘)来充当临时表空间的保存地址,只能用老办法做软链。

​ 需要注意的一点就是:有时执行SQL请求时会产生临时表,极端情况下可能导致临时表空间文件暴涨,所以为了避免以后再出现类似的情况,一定要限制临时表空间的最大上限,超过上限时,需要生成临时表的SQL无法被执行(一般这种SQL效率也比较低,可借此机会进行优化)。

(6)errorlog:错误日志记录了MySQL Server每次启动和关闭的详细信息,以及运行过程中所有较为严重的警告和错误信息。如图1-11所示,可以用参数log-error[=file_name]选项来开启MySQL错误日志,该选项指定保存错误日志文件的位置。

(7)slow.log:如果配置了MySQL的慢查询日志,MySQL就会将运行过程中的慢查询日志记录到slow_log文件中。MySQL的慢查询日志是MySQL提供的一种日志记录,用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL。如图1-12所示,参数slow_query_log_file指定慢查询日志文件的存放路径;参数long_query_time的默认值为10,意思是运行10s以上的语句。默认情况下,MySQL数据库并不启动慢查询日志,需要我们手动来设置这个参数。慢查询日志支持将日志记录写入文件,也支持将日志记录写入数据库表。

(8)general_log:如图1-13所示,参数general_log=off表明没有启用通用查询日志,如果配置了通用查询日志,将记录建立的client连接和运行的语句。参数general_log_file表明通用查询日志位置及名字,MySQL将运行过程中的所有SQL都记录在此文件中。

( 9 ) 数 据 库 路 径 : 可 以 看 到 系 统 数 据 库 mysql 、 sys 、performance_schema和用户自定义的数据库test、mldn(展开具体的路径之后是具体的每个数据库自己的对象)。

  • ①系统数据库:系统数据库包括以下几种。

    • information_schema系统数据库,提供了数据库的元数据信息,是数据库的数据,比如数据库的名字和数据库中的表名、字段名、字段类型等,可以说是数据库的数据字典信息。这个库中的信息并非物理地保存在表中,而是动态地去读取其他文件得到的。例如,上面一开始提到的共享表空间,用户数据中的 对 象 ( 比 如 表 结 构 等 ) 就 都 保 存 在 共 享 表 空 间 中 ,information_schema库中的一些信息可以认为是直接映射到共享表空间中的信息的。
    • performance_schema系统数据库,是数据库性能相关的信息的数据,记录的是数据库服务器的性能参数,保存历史事件汇总信息,为MySQL服务器性能评估提供参考信息。
    • sys系统数据库,可以根据sys库中的数据快速了解系统的运行信息,方便地查询出数据库的信息,在性能瓶颈、自动化运维等方面都有很大的帮助。sys库中的信息通过视图的方式将information_schema和performance_schema库中的数据结合起来,可以得到更加直观和容易理解的信息。
    • mysql系统数据库,存储系统的用户权限信息及帮助信息。新建的用户、用户的权限信息等都存储在mysql库。例如,在修改MySQL的root密码时,要先调用mysql这个系统库再执行用户、授权等操作。
  • ②用户数据库:用户数据库(如mldn)实际上是一个目录,保存了数据库中的表以及数据信息。图1-14是一个典型的数据库目录下的文件信息。对于InnoDB引擎的表,InnoDB为独立表空间模式,每个数据库的每个表都会生成一个数据空间(而不是在共享表空间ibdata1文件中)。例如,member表分别对应两个文件:一个是member.frm,存储的是表结构信息;另一个是member.ibd,存储的是表中的数据。同理,customers表也是两个文件。另外,还有一个db.opt文件,是用来记录该库的默认字符集编码和字符集排序规则的。

第2章 InnoDB存储引擎体系结构

​ 从MySQL 5.5版本开始,InnoDB是默认的表存储引擎,特点是支持事务、支持数据行锁、支持多版本并发MVCC、支持外键。InnoDB存储引擎的体系结构如图2-1所示,包括内存池、后台线程和底层的数据文件

​ InnoDB存储引擎有各种缓冲池(Buffer Pool),这些缓冲块组成了一个大的InnoDB存储引擎内存池,主要负责的工作是:

  • 维护所有进程/线程需要访问的多个内部数据结构;
  • 缓存磁盘上的数据,方便快速读取,同时在对磁盘文件修改之前进行缓存;
  • 重做日志缓存等。

后台线程的主要作用是:

  • 负责刷新内存池中的数据,保证缓冲池中的内存缓存的是最新数据;
  • 将已修改数据文件刷新到磁盘文件;
  • 保证数据库发生异常时InnoDB能恢复到正常运行的状态。

2.1 缓冲池

InnoDB存储引擎是基于磁盘存储的。由于CPU速度和磁盘速度之间的鸿沟,InnoDB引擎使用缓冲池技术来提高数据库的整体性能。

​ 在数据库中进行读取页的操作时,将从磁盘读到的页存放在缓冲池中,下一次读取相同的页时首先判断该页是不是在缓冲池中,如果在,就称该页在缓冲池中被命中,直接读取该页对于数据库中页的修改操作,首先修改在缓冲池页中,然后以一定的频率刷新到磁盘。这里需要注意的是,页从缓冲池刷新回磁盘的操作并不是在每次页发生更新时触发,而是通过一种称为checkpoint的机制刷新回磁盘,这也是为了提高数据库的整体性能。

InnoDB存储引擎的缓存机制和MyISAM的最大区别就在于InnoDB缓冲池不仅仅缓存索引,还会缓存实际的数据。InnoDB存储引擎可以使用更多的内存来缓存数据库相关的信息,这对于内存价格不断降低的时代无疑是很吸引人的特性。

​ InnoDB Buffer Pool(缓冲池)是InnoDB性能提升的核心,不像Query Cache仅存的SQL对应的结果集,Buffer Pool上可以完成数据的更新变化、减少随机I/O的操作、提高写入性能,而Query Cache最忌讳表的数据更新,会导致相应的Cache失效,带来额外系统消耗。在实际中,尽可能增大innodb_buffer_pool_size的大小,把频繁访问的数据都放到内存中,尽可能减少InnoDB对于磁盘I/O的访问,把InnoDB最大化为一个内存型引擎

​ 如图2-2所示,innodb_buffer_pool_size参数用来设置InnoDB的缓冲池(InnoDB BufferPool)的大小,也就是缓存用户表及索引数据的最主要缓存空间这个对InnoDB整体性能影响也最大,一般可以设置为50%到80%的内存大小。在专用数据库服务器上,可以将缓冲池大小设置得大些,多次访问相同的数据表数据所需要的磁盘I/O就更少。在MySQL 5.7版本之前,调整innodb_buffer_pool_size大小必须在my.cnf配置里修改,然后重启mysql进程才可以生效。如今到了MySQL 5.7版本,可以直接动态调整这个参数修改Buffer Pool的大小,方便了很多。尤其是在服务器内存增加之后,运维人员更不能粗心大意,要记得调大innodb_buffer_pool_size这个参数。在调整innodb_buffer_pool_size期间,用户的请求将会阻塞,直到调整完毕,所以请勿在业务高峰期调整,最好在凌晨两三点低峰期调整。

在InnoDB存储引擎中,缓冲池中页的大小默认为16KB,通过LRU(Letest Recent Used,最近最少使用)算法来进行数据页的换进换出操作。也就是说,最频繁的页放在LRU列表的前端,而最少使用的页放在LRU列表的尾端。缓冲池不能存放新读取到的页时,会释放LRU列表中尾端的页。

InnoDB存储引擎对传统的LRU算法做了一些优化。因为如果直接将最新读取的页放到LRU列表的首部,那么某些SQL操作(比如全表扫描或者索引扫描)如果要访问很多页,甚至是全部页(通常来说仅在此次查询操作中需要,并不是活跃的热点数据),很可能会将活跃的热点数据挤出LRU列表。在下一次需要读取这些热点数据页时,InnoDB存储引擎则需要再一次从磁盘读取。所以InnoDB存储引擎的LRU列表中还加入了midpoint位置,即新读取的页并不是直接放到LRU列表首部,而是放到LRU列表的midpoint位置。这个算法在InnoDB存储引擎下称为midpoint insertion strategy , 默 认 该 位 置 在 LRU 列 表 长 度 的 5/8 处 。midpoint的位置可由参数innodb_old_blocks_pct控制,如图2-3所示。

​ 在图2-3中,innodb_old_blocks_pct默认值为37,表示新读取的页插入到LRU列表尾端37%的位置。在InnoDB存储引擎中,把midpoint之后的列表称为old列表,之前的列表称为new列表 。 new 列 表 的 数 据 可 以 简 单 理 解 为 都 是 活 跃 的 热 点 数 据 。 InnoDB 还 有 一 个 参 数innoDB_old_blocks_time,表示页读取到midpoint位置后需要等待多久才会被加入到LRU列表的热端。

​ 在MySQL 5.6之前的版本里,如果一台高负荷的机器重启后内存中大量的热数据被清空,buffer中的数据就需要重新预热。所谓预热,就是等待常用数据通过用户调用SQL语句从磁盘载入到内存,这样高峰期间性能就会变得很差,连接数就会很高。通常要手动写一个脚本或存储过程来预热。

​ MySQL 5.6之后的版本提供了一个新特性来快速预热buffer_pool缓冲池,如图2-4所示。参数innodb_buffer_pool_dump_at_shutdown=ON表示在关闭MySQL时会把内存中的热数据保存在磁盘里ib_buffer_pool文件中,其保存比率由参数innodb_buffer_pool_dump_pct控制,默认为25% 。 参 数 innodb_buffer_pool_load_at_startup=ON 表 示 在 启 动 时 会 自 动 加 载 热 数 据 到Buffer_Pool缓冲池里。这样,始终保持热数据在内存中。

​ 只有在正常关闭MySQL服务或者pkill mysql时才会把热数据dump到内存,机器宕机或者pkill -9 mysql时是不会dump的。

2.2 change buffer

​ change buffer的主要目的是将对二级索引的数据操作缓存下来,以减少二级索引的随机I/O,并达到操作合并的效果。

​ 工作原理是有一个或多个非聚集索引,且该索引不是表的唯一索引时,插入时数据会按主键顺序存放,但叶子节点需要离散地访问非聚集索引页,插入性能会降低;此时,插入缓冲生效,先判断非聚集索引页是否在缓冲池中,若在则直接插入;若不在,则先放入一个插入缓冲区,再以一定的频率执行插入缓冲和非聚集索引页子节点的合并操作。在MySQL 5.5之前的版本中,由于只支持缓冲insert操作,因此最初叫作insert buffer;后来的版本中支持了更多的操作类型缓冲,所以才改叫change buffer。

​ 如图2-5所示,将对索引的更新记录存入insert buffer中,而不是直接调入索引页进行更新;择机进行merge insert buffer的操作,将insert buffer中的记录合并(merge)到真正的辅助索引中,大大提高了插入的性能。

​ 二级索引通常是非唯一的,插入也是很随机的顺序,更新删除也都不是在邻近的位置,所以change buffer就避免了很多随机I/O的产生,将多次操作尽量变为少量的I/O操作。change buffer也是可以持久化的,将change buffer中的操作应用到原数据页、得到最新结果的过程称为merge。

​ change buffer合并在有大量的二级索引页更新或有很多影响行的情况下会花费很长的时间。注意,change buffer会占用InnoDB Buffer Pool的部分空间,在磁盘上change buffer会占用共享表空间,所以在数据库重启后,索引变更仍然被缓存。

​ 如图2-6所示,参数innodb_change_buffering表示缓存所对应的操作,all值表示缓存insert、delete、purges操作;innodb_change_buffer_max_size参数用于配置change buffer在Buffer Pool中所占的最大百分比,默认是25%,最大可以设置为50%。

​ 可以在show engine innodb status\G命令结果中查看change buffer的信息。在insert buffer and adaptive hash index部分中,merged operations代表了辅助索引页与change buffer的合并操作次数。使用下面的语句也能监控:

SELECT NAME, COMMENT FROM INFORMATION_SCHEMA.INNODB_METRICS WHERE NAME LIKE '%ibuf%' \G;

因此,对于写多读少的业务来说,页面在写完以后马上被访问到的概率比较小,此时change buffer的使用效果最好,常见的就是账单类、日志类的系统

2.3 自适应哈希索引

​ 哈希(hash)是一种非常快的查找方法,一般情况下查找的时间复杂度为O(1),即一般仅需要一次查找就能准确定位。B+Tree的查找次数则取决于B+Tree的高度,在大多数的生产环境中,B+Tree的高度一般为3到5层,故需要3~5次的查询。

InnoDB存储引擎会监控对表上二级索引的查找。如果发现某二级索引被频繁访问,二级索引就成为热数据;如果观察到建立哈希索引可以带来速度的提升,则建立哈希索引,所以称之为自适应(adaptive)的,即自适应哈希索引(Adaptive Hash Index,AHI)。

​ 经常访问的二级索引数据会自动被生成到hash索引里面(最近连续被访问3次的数据),自适应哈希索引通过缓冲池的B+Tree构造而来,因此建立的速度很快,而且不需要将整个表都建立哈希索引,InnoDB存储引擎会自动根据访问的频率和模式来为某些页建立哈希索引。

​ 自适应哈希索引会占用InnoDB Buffer Pool,而且只适合搜索等值的查询,如select * from table where index_col=‘xxx’;对于其他查找类型,如范围查找,是不能使用的。MySQL自动管理,人为无法干预。

​ 查看当前自适应哈希索引的使用状况可以使用show engine innodb status\G命令,通过hash searches、non-hash searches计算自适应哈希索引带来的收益以及付出,确定是否开启自适应哈希索引。

​ 对于某些工作负载,如使用like和%的范围查询以及高并发的joins,不适合使用自适应哈希索引,维护哈希索引结构的额外开销会带来严重性能损耗。这种情况更适合于禁用自适应哈希索引,建议关掉,尽管默认情况下仍然启用。可以通过 “set global innodb_adaptive_hash_index=off/on”命令来关闭或打开该功能。

2.4 redo log buffer

redo log buffer是一块内存区域,存放将要写入redo log文件的数据。redo log buffer大小是通过设置innodb_log_buffer_size参数来实现的。redo log buffer会周期性地刷新到磁盘的redo log file中。一个大的redo log buffer允许大事务在提交之前不写入磁盘的redo log文件。因此,如果有事务需要update、insert、delete许多记录,则可增加redo log buffer来节省磁盘I/O。

​ 参数innodb_flush_log_at_trx_commit选项控制redo log buffer的内容何时写入redo log file,即控制redo log flush的频率。

​ innodb_flush_log_at_trx_commit的参数取值及其说明如下:

  • 设置为0:在提交事务时,InnoDB不会立即触发将缓存日志log buffer写到磁盘文件的操作,而是每秒触发一次缓存日志回写磁盘操作,并调用操作系统fsync刷新I/O缓存。

  • 设置为1:每次事务提交时MySQL都会立即把log buffer的数据写入redo log file,并且调用操作系统fsync刷新I/O缓存(刷盘操作)。值为1时,每次事务提交都持久化一次,当然是最安全的,但是数据库性能会受影响,I/O负担重,适合对安全要求极高的交易系统场景(建议配置SSD硬盘提高I/O能力)。

  • 设置为2:每次事务提交时MySQL都会把redo log buffer的数据写入redo log file,但是flush(刷到磁盘)操作并不会同时进行,而是每秒执行一次flush(磁盘I/O缓存刷新)。注意,由于进程调度策略问题,并不能保证百分之百的“每秒”。

​ 刷写其实是两个操作,即刷(flush)和写(write)。区分这两个概念(两个系统调用)是很重要的。在大多数的操作系统中,把InnoDB的log buffer(内存)写入日志(调用系统调用write),只是简单地把数据移到操作系统缓存(内存)中,并没有实际持久化数据。

通常设置为0的时候,mysqld进程的崩溃会导致上一秒钟的所有事务数据丢失

当该值为2时,表示事务提交时不写入重做日志文件,而是写入文件系统缓存中,当DB数据库发生故障时能恢复数据,如果操作系统也出现宕机,就会丢失掉文件系统没有及时写入磁盘的数据

设为1当然是最安全的,适合数据安全性要求非常高的而且磁盘I/O写能力足够支持业务,比如订单、交易、充值支付消费系统。如果对数据一致性和完整性要求不高,完全可以设为2,推荐使用带蓄电池后备电源的缓存cache,防止系统断电异常。如果只要求性能,例如高并发写的日志服务器,就设置为0来获得更高性能。

2.5 double write

double write(两次写)技术的引入是为了提高数据写入的可靠性。这里先说明一下page页坏的问题。因为数据库中一个page的大小是16KB,数据库往存储上写数据是以更小的单位进行的,这就产生了一个问题:当发生数据库宕机时,可能InnoDB存储引擎正在写入某个页到表中,而这个页只写了一部分,比如16KB的页,只写了前4KB,之后就发生了宕机,这种情况被称为部分写失效(partial page write)。在InnoDB存储引擎未使用double write技术前,曾经出现过因为部分写失效而导致数据丢失的情况。

​ double write的原理是:每次写入一个page时,先把page写到double write buffer中。如果在写double write buffer时发生了意外,但是数据文件中原来的page不受影响,这样在下次启动时可以通过InnoDB的redo log进行恢复。在写double write buffer成功后,MySQL会把double write buffer的内容写到数据文件中,如果在这个过程又出现了意外,没有关系,重启后MySQL可以从double write buffer找到好的page,再用好的page去覆盖磁盘上坏的page,解决page坏的问题。这就是double write,说白了就是一种备份镜像的思想

​ 如图2-7所示,double write默认存放在ibdata1共享表空间里,默认大小为2MB,写之前将脏页写入到innodb buffer中的double write buffer(2MB)中,将2MB的buffer数据直接写入共享表空间ibdata1的double write段中。若写共享表空间的double write失败了,没有关系,因为此时的数据文件ibd中的数据是完整干净的,处于一致的状态,可以通过redo log进行恢复;如果是写到ibd文件时发生了宕机,此刻在原来的ibdata1中存在副本,可以直接覆盖到ibd文件(对应的页)中去,然后用redo log进行恢复。

​ 肯定有读者要问,MySQL redo log不是已经记录了所有的数据历史记录了吗?

​ 要弄明白这个问题,首先要了解一下MySQL redo log里面记录的是什么东西。日志分为物理日志和逻辑日志物理日志就是直接记录数据、记录被修改的页的偏移量,优点就是不依赖原页面的内容,用日志的内容可以直接覆盖到磁盘上面,缺点是占用的空间太多逻辑日志的优点是比较简洁,而且占用的空间要小,缺点是需要依赖原page内容,而且会有部分执行和操作一致性的问题

​ 所以说MySQL redo log是物理逻辑的,它将物理日志和逻辑日志相结合,取其利,避其害,从而达到一个更好的状态,具体说明就是:物理表示记录的日志针对的是页(page)的修改,为每个页上的操作单独记日志;逻辑表示记录日志的内容是逻辑的,比如物理上来说要修改Page Header页头的内容、要修改相邻记录里的链表指针等。这些本是一些物理操作,而InnoDB为了节省日志量,设计为逻辑处理的方式。这样的一个MySQL redo log其实仍然没有解决数据的一致性问题。如果在redo log应用到磁盘时,在写一个Page到磁盘时发生了故障,可能导致Page Header的记录数被加1(表示此Page已恢复完成),但是页内的逻辑日志发生了故障,这时数据就不一致了。MySQL用double write方法来解决此问题。

​ 可能有人会问double write对性能影响大吗?如果页大小是16KB,那么就有128个页(1MB)需要写,但是128个页写入到共享表空间是1次I/O完成的,也就是说double write写开销是1+128次。其中,128次是写数据文件表空间。在传统的机械式硬盘中,double write buffer写入是顺序操作。相对于数据文件写入这样的随机写操作来说,顺序写入的代价还是小的。在新型的SSD存储中,double write buffer导致数据重复写入对于SSD寿命有较大影响。如果SSD设备支持原子写,那么在MySQL中可以通过设置参数innodb_doublewrite=0关掉double write的功能。

​ 查看double write的工作情况,如图2-8所示。执行命令“show global status like ‘innodb_dblwr%’\G”,可以观察到double write运行的情况。这里double write一共写了18445 页 , 但 实 际 的 写 入 次 数 为 434 。 如 果 发 现 你 的 系 统 在 高 峰 时Innodb_dblwr_pages_written:Innodb_dblwr_writes远小于64:1,那么说明你的系统写入压力并不是很高。

​ 其实两次写并不是什么特性或优点,只是一个被动解决方案而已。这个问题的本质就是磁盘在写入时不能保证MySQL数据页面16KB的一次性原子写,所以才有可能产生页面断裂的问题。有些厂商从硬件驱动层面做了优化,可以保证16KB(或其他配置)数据的原子性写入,那么两次写就完全没有必要了。在以前的一个项目中,Fusion-I/O的SSD卡是支持原子写的技术,不需要开启两次写即可提升MySQL数据写入延迟,同时延长SSD存储寿命。

2.6 InnoDB后台线程

2.6.1 InnoDB主线程

​ master thread是InnoD存储引擎非常核心的一个在后台运行的主线程,相当重要。它做的主要工作包括但不限于:将缓冲池中的数据异步刷新到磁盘,保证数据的一致性,包括脏页的刷新、合并插入缓冲等

​ master thread的线程优先级别最高,其内部由几个循环组成:主循环(loop)、后台循环(background loop)、刷新循环(flush loop)、暂停循环(suspend loop)。master thread会根据数据运行的状态在loop、background loop、flush loop和suspend loop这4个循环之间进行切换。loop称为主循环,因为大多数的操作都在这个循环中,其中有两大部分操作:每秒钟的操作和每10秒的操作。

​ loop循环通过thread sleep来实现,这意味着所谓的每秒一次或每10秒一次的操作是不精确的。在负载很大的情况下可能会有延迟,只能说大概在这个频率下。当然InnoDB源代码中还采用了其他的方法来尽量保证这个频率。

每秒一次的操作包括:

  • 日志缓冲刷新到磁盘,即使这个事务还没有提交(总是)。

  • 执行合并插入缓冲的操作(可能)。

  • 刷新缓冲池中的脏页到磁盘(可能)。

  • 如果当前没有用户活动,切换到background loop(可能)。

​ 需要注意的是,即使某个事务还没有提交,InnoDB存储引擎仍然会每秒将重做日志缓冲中的内容刷新到重做日志文件。这一点是必须知道的,可以很好地解释为什么再大的事务提交的时间也是很快的。合并插入缓冲(insert buffer)并不是每秒都发生,InnoDB存储引擎会判断当前一秒内发生的I/O次数是否小于5次,如果小于5次,InnoDB会认为当前的I/O压力很小,可以执行合并插入缓冲的操作。同时InnoDB存储引擎通过判断当前缓冲池中的脏页比例(buf_get_modified_ratio_pct)是否超过了配置文件中innodb_max_dirty_pages_pct这个参数来决定是否需要进行磁盘同步操作,如果超过了这个阈值,InnoDB存储引擎认为需要做磁盘同步操作。

每10秒的操作包括如下内容:

  • 刷新脏页到磁盘(可能)。

  • 执行合并插入缓冲的操作(总是)。

  • 将日志缓冲刷新到磁盘(总是)。

  • 删除无用的undo页(总是)。

  • 产生一个检查点checkpoint(总是)。

​ 这里需要注意的是,不同于1秒操作时可能发生的合并插入缓冲操作,这次的合并插入缓冲操作总会在这个阶段进行。InnoDB存储引擎会再执行一次将日志缓冲刷新到磁盘的操作,与每秒发生的操作是一样的。InnoDB存储引擎会执行一步full purge操作,即删除无用的undo页。对表执行update、delete这类操作时,原生的行被标记为删除,但是因为一致性读的关系,需要保留这些行版本的信息,但是在full purge过程中,InnoDB存储引擎会判断当前事务系统中已被删除的行是否可以删除,比如有时可能还有查询操作需要读取之前版本的undo信息,如果可以,InnoDB会立即将其删除。

2.6.2 InnoDB后台I/O线程

​ 在InnoDB存储引擎中大量使用了AIO(Async I/O)来处理写I/O请求,这样可以极大地提高数据库的性能。I/O线程的工作主要是负责这些I/O请求的回调(call back)处理。InnoDB 1.0版本之前共有4个I/O线程,分别是write、read、insert buffer和log I/O thread。在Linux平台 下 , I/O 线 程 的 数 量 不 能 进 行 调 整 , 但 是 在 Windows 平 台 下 可 以 通 过 参 数innodb_file_io_threads来增大I/O线程。从InnoDB 1.0.x版本开始,read thread和write thread 分 别 增 大 到 了 4 个 , 并 且 不 再 使 用 innodb_file_io_threads 参 数 , 而 是 分 别 使 用innodb_read_io_threads和innodb_write_io_threads参数进行设置,如此调整后,在Linux平台上就可以根据CPU核数来更改相应的参数值了。

​ 假如CPU是2颗8核的,那么可以在配置文件my.cnf中设置:

innodb_read_io_threads=8
innodb_write_io_threads=8

2.6.3 InnoDB脏页刷新线程

​ MySQL 5.6版本以前,脏页的清理工作交由master线程处理。page cleaner thread是5.6.2版本引入的一个新线程,实现从master线程中卸下缓冲池刷脏页的工作。为了进一步提升扩展性和刷脏效率,在5.7.4版本里引入了多个page cleaner线程,从而达到并行刷脏的效果。

​ 如图2-9所示,如果调整参数innodb_page_cleaners,需要在配置文件my.cnf中添加innodb_page_cleaners=num值(默认是1;最大可以是64,也就是会有64个page cleaner线程并发清理脏页)。

​ 如何判断是否要修改innodb_page_cleaners 呢?如图2-10所示,参数Innodb_buffer_pool_wait_free标志着脏页有没有成为系统的性能瓶颈,如果它的值很大,则需要增加innodb_page_cleaners值,同时增加写线程。

​ 通常,对于Buffer Pool的写发生在后台,当InnoDB需要读或创建一个数据页但是没有干净的 可 用 页 时 , InnoDB 就 会 为 使 等 待 的 操 作 能 完 成 而 先 将 一 些 脏 页 刷 入 磁 盘 。Innodb_buffer_pool_wait_free就是等待操作的实例数。如果innodb_buffer_pool_size的大小设置适当,这个值就会很小,甚至为0。

2.6.4 InnoDB purge线程

purge thread负责回收已经使用并分配的undo页。由于进行DML语句的操作都会生成undo,因此系统需要定期对undo页进行清理,可使用purge操作。

​ 为什么MySQL InnoDB需要purge操作呢?明确这个问题的答案,首先还得从InnoDB的并发机制开始。为了更好地支持并发,InnoDB的多版本一致性读采用了基于回滚段的方式。另外,对于更新和删除操作,InnoDB并不是真正地删除原来的记录,而是设置记录的delete mark为1。为了解决数据page和undo log膨胀的问题,需要引入purge机制进行回收。

​ purge数据产生的背景是undo log和mark deleted数据:

  • (1)undo log保存了记录修改前的镜像。在InnoDB存储引擎中,undo log分为insert undo log和update undo log。insert undo log是指在insert操作中产生的undo log。由于insert操作的记录只是对本事务可见,其他事务不可见,因此undo log可以在事务提交后直接删除,不需要purge操作。update undo log是指在delete和update操作中产生的undo log。该undo log会被后续用于MVCC当中,因此不能在提交的时候删除。提交后会放入undo log的链表,等待purge线程进行最后的删除。

  • (2)当我们删除数据行时,对数据页中要删除的数据行做标记“deleted”,事务提交(速度快);后台线程purge线程对数据页中有“deleted”标签的数据行进行真正的删除。purge操作默认是在master thread中完成的。从MySQL 5.6开始,为了减轻master thread的工作、提高CPU使用率以及提升存储引擎的性能,把purge thread单独从master thread分离出来,已经支持多个purge线程同时进行。提供了参数innodb_purge_threads来控制做purge操作的后台线程数,从MySQL 5.7.8开始,这个参数默认是4,如果实例的写压力比较大,则可调整innodb_purge_threads=8,增加并发purge线程数,最大可以设置为32。

2.7 redo log

​ 首先明确一下InnoDB修改数据的基本流程。当我们想要修改DB上某一行数据的时候,InnoDB是把数据从磁盘读取到内存的缓冲池上进行修改。这个时候数据在内存中被修改,与磁盘中相比就存在了差异,我们称这种有差异的数据为<脏页>。InnoDB对脏页的处理不是每次生成脏页就将脏页刷新回磁盘,因为这样会产生海量的I/O操作,严重影响InnoDB的处理性能。既然脏页与磁盘中的数据存在差异,那么如果在此期间DB出现故障就会造成数据丢失。为了解决这个问题,redo log就应运而生了

​ 我们着重看看redo log是怎么一步步写入磁盘的。redo log本身由两部分所构成,即重做日志缓冲(redo log buffer)和重做日志文件(redo log file)。这样的设计同样也是为了调 和 内 存 与 磁 盘 的 速 度 差 异 。 InnoDB 写 入 磁 盘 的 策 略 可 以 通 过innodb_flush_log_at_trx_commit这个参数来控制。

​ DB宕机后重启,InnoDB会首先去查看数据页中LSN的数值,即数据页被刷新回磁盘的LSN(LSN实际上就是InnoDB使用的一个版本标记的计数)的大小,然后去查看redo log的LSN大小。如果数据页中的LSN值大,就说明数据页领先于redo log刷新回磁盘,不需要进行恢复;反之,需要从redo log中恢复数据。

​ 当一个日志文件写满后,InnoDB会自动切换到另一个日志文件,但切换时会触发数据库检查点checkpoint(checkpoint所做的事就是把脏页刷新回磁盘,当DB重启恢复时只需要恢复checkpoint之后的数据即可),导致InnoDB缓存脏页的小批量刷新,明显降低InnoDB的性能。 可以通过增大log file size避免一个日志文件过快被写满,但是如果日志文件设置得过大,恢复时将需要更长的时间,同时也不便于管理。一般来说,平均每半个小时写满一个日志文件比较合适。

​ 参数innodb_log_buffer_size决定InnoDB重做日志缓冲池的大小。对于可能产生大量更新记录的大事务,增加innodb_log_buffer_size的大小,可以避免InnoDB在事务提交前就执行不必要的日志写入磁盘操作。因此,对于会在一个事务中更新、插入或删除大量记录的应用,可以通过增大innodb_log_buffer_size来减少日志写磁盘操作,提高事务处理性能。

2.8 undo log

undo log是InnoDB MVCC事务特性的重要组成部分当我们对记录做了变更操作时就会产生undo记录,undo记录默认被记录到系统表空间(ibdata)中,但从MySQL 5.6开始,也可以使用独立的undo表空间。

​ 在InnoDB中,insert操作在事务提交前只对当前事务可见,undo log在事务提交后即会被删除,因为新插入的数据没有历史版本,所以无须维护undo log;对于update、delete操作,则需要维护多版本信息

​ 举个undo log作用的例子:Session1会话(以下简称S1)和Session2会话(以下简称S2)同时访问(不一定同时发起,但S1和S2事务有重叠)同一数据A,S1想要将数据A修改为数据B,S2想要读取数据A的数据。如果没有MVCC(Multi-Version Concurrency Control,多版本并发控制)机制就只能依赖锁了,谁拥有锁谁先执行,另一个等待,但是高并发下的效率很低。InnoDB存储引擎通过MVCC多版本控制的方式来读取当前执行时间数据库中行的数据,如果读取的行正在执行delete或update操作,这时读取操作不会因此等待行上锁的释放;相反,InnoDB会去读取行的一个快照数据(undo log),从历史快照(undo log链)中获取旧版本数据来保证数据一致性。由于历史版本数据存放在undo页当中,对数据修改所加的锁对于undo页没有影响,因此不会影响用户对历史数据的读,从而达到非一致性锁定读,提高并发性能。

​ 如果出现了错误或者用户手动执行了rollback,系统可以利用undo log中的备份将数据恢复到事务开始之前的状态。与redo log不同的是,磁盘上不存在单独的undo log文件,它存放在数据库内部的特殊段(segment)中。

​ MySQL 5.6以后的版本支持把undo log分离到独立的表空间,并放到单独的文件目录下。采用独立undo表空间,再也不用担心undo会把ibdata1文件搞大,同时也给我们部署不同I/O类型的文件位置带来了便利。对于并发写入型负载,我们可以把undo文件部署到单独的高速存储设备上。

2.9 Query Cache

​ 在这个“Cache为王”的时代,我们总是通过不同的方式去缓存我们的结果,从而提高响应效率,但是一个缓存机制是否有效、效果如何是需要好好思考的问题。在MySQL中的Query Cache是一个适用较少情况的缓存机制。

如果你的应用对数据库的更新很少,那么Query Cache将会作用显著。例如,比较典型的博客系统,一般博客更新相对较慢,数据表相对稳定不变,这时Query Cache的作用会比较明显。Query Cache有如下规则:如果数据表被更改,那么和这个数据表相关的全部Cache都会无效,并会被删除。这里“数据表更改”包括insert、update、delete、truncate、alter table、drop table、drop database等。举个例子,如果数据表posts访问频繁,那么意味着它的很多数据会被Query Cache缓存起来,但是每一次posts数据表的更新,无论是不是影响Cache的数据,都会将全部和posts表相关的Cache清除。如果数据表更新频繁,那么Query Cache将会成为系统的负担,会降低系统13%的处理能力。在OLTP(联机事务处理)的业务场景下,Query Cache建议关闭。如果可以应用层实现缓存,那么Query Cache可以忽略

​ 下面是关于Query Cache的相关参数:

  • query_cache_size:设置Query Cache所使用的内存大小,默认值为0。大小必须是1024的整数倍,如果不是整数倍,MySQL会自动调整降低最小量以达到1024的倍数。

  • query_cache_type:控制Query Cache功能的开关,可以设置为0(OFF)、1(ON)和 2(DEMAND)3种。其中,0表示关闭Query Cache功能,任何情况下都不会使用Query Cache;1表示开启Query Cache功能,但是当SELECT语句中使用的SQL_NO_CACHE提示后将不使用Query Cache;2表示开启Query Cache功能,但是只有当SELECT语句中使用了SQL_CACHE提示后才使用Query Cache。

因为任何更新操作都会导致QC失效,在并发高的时候也是有影响的,还有Bug,偶尔来个Crash,所以MySQL的Query Cache在大部分情况下只是鸡肋而已,建议全面禁用。想要彻底关闭Query Cache,建议在一开始就设置query_cache_type=0,在MySQL 5.7中Query Cache默认为关闭。

第3章 MySQL事务和锁【*】

​ 为什么需要事务,其实用脚趾头想想也能知道它的重要性。举个简单的例子:一个用户提交了一个订单,这条数据里包含了两个信息,即用户信息和购买的商品信息。现在需要把它们分别存到用户表和商品表,如果不采用事务,可能会出现商品信息插入成功而没有用户信息,这时就会出现用户付了钱却得不到商品这样尴尬的事情;如果采用事务,就可以保证用户信息和商品信息都插入成功,该次事务才算成功,也就不会出现前面的问题了。InnoDB数据库引擎支持事务。事务具有ACID(原子性、一致性、隔离性和持久性),还有不同的隔离级别(具有不同的隔离性)。事务的隔离级别是通过锁的机制来实现的。

锁在计算机中是协调多个进程或线程并发访问某一资源的一种机制。在数据库中,除了传统的计算资源(CPU、RAM、I/O等)争用之外,数据也是一种供许多用户共享访问的资源。数据库在进行并发访问的时候会自动对相应的对象进行加锁,以保证数据并发访问的一致性。InnoDB存储引擎既支持行级锁,也支持表级锁,但默认情况下采用行级锁。

3.1 MySQL事务概述

​ 事务可以只包含一条SQL语句,也可以包含多条复杂的SQL语句。事务中的所有SQL语句被当作一个操作单元,换句话说,事务中的SQL语句要么都执行成功,要么全部执行失败。事务内的SQL语句被当作一个整体进行操作。

​ 我们来看一个场景,这个场景就是“转账”。例如,“天都银行”有很多用户,目前A用户账户上的余额为9000元,B用户账上的余额为4000元,现在A用户要向B用户转账2000元。当转账结束以后,A用户账户上的余额应该为7000元,B用户账户上的余额应该为6000元。

​ 上述过程在数据库中应该转换为如下操作:

  • 操作1:修改A用户账户对应的余额记录,即9000-2000。

  • 操作2:修改B用户账户对应的余额记录,即4000+2000。

​ 上述操作好像没有问题,如果数据库刚刚完成操作1,很不凑巧停电了,过了三分钟,来电了,当我们再次查看数据库时,会发现A用户余额为7000,比停电之前少2000,B用户的账户余额仍然为4000,与停电之前一样。出现这种情况是因为数据库只完成了操作1,而没来得及完成操作2,2000元就凭空消失了。我们应该防止这样的惨剧发生,解决方法就是使用事务。

​ 我们之前说过,事务中的所有SQL语句都被当作一个整体,要么全部执行成功,要么在其中某些操作执行失败后回滚到最初状态,就好像什么都没有发生过一样。利用事务这个特性,就可以解决之前的问题。我们可以把转账的SQL语句写入到事务中,具体如下:

  • begin事务开始
  • update A用户余额- 2000
  • update B用户余额+ 2000
  • commit提交事务(事务结束)

​ 利用事务完成上述操作,即使数据库刚刚将A用户账户余额减去2000时停电了,由于事务的特性,当再次使用数据库时,也不会出现A用户余额变为7000、B用户余额仍然为4000的情况。为什么呢?事务其实和一个操作没有什么太大的区别,它是一系列数据库操作(可以理解为SQL)的集合,如果事务不具备原子性,就没有办法保证同一个事务中的所有操作都被执行或者未被执行了,整个数据库系统就既不可用也不可信了。

​ 想要保证事务的原子性,就需要在异常发生时对已经执行的操作进行回滚。在MySQL中,恢复机制是通过回滚日志(undo log)实现的,所有事务进行的修改都会先记录到回滚日志中,然后对数据库中的对应行进行写入。这个过程其实非常好理解,为了能够在发生错误时撤销之前的全部操作,肯定是需要将之前的操作都记录下来的,这样在发生错误时才可以回滚。回滚日志除了能够在发生错误或者用户执行ROLLBACK时提供回滚相关的信息,还能够在整个系统发生崩溃、数据库进程直接被杀死后,当用户再次启动数据库进程时立刻通过查询回滚日志将之前未完成的事务进行回滚,这也就需要回滚日志必须先于数据持久化到磁盘上,是我们需要先写日志后写数据库的主要原因。

MySQL中,InnoDB存储引擎是支持事务的,而且完全符合ACID的特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

  • 原子性:整个事务中的所有操作要么全部执行成功,要么全部执行失败后回滚到最初状态。

  • 一致性:数据库中的数据在事务操作前和事务处理后必须都满足业务规则约束,比如A和B账户总金额在转账前和转账后必须保持一致。

  • 隔离性:一个事务在提交之前所做出的操作是否能为其他事务可见。由于不同的场景需求不同,因此针对隔离性来说有不同的隔离级别。

  • 持久性:一旦提交,事务所做出的修改就会永久保存,即使数据库崩溃,修改的数据也不会丢失。

​ 在MySQL中,既可以通过START TRANSACTION语句来开始一个事务,也可以使用别名BEGIN语句。事务结束时可以使用COMMIT语句提交事务或通过ROLLBACK语句回滚事务撤销修改。

​ MySQL默认情况下开启了一个自动提交的模式autocommit,一条语句被回车执行后便生效了,变更会保存在MySQL的文件中,无法撤销。当使用相应语句(比如BEGIN)显式声明开始一个事务时,autocommit默认会是关闭状态。无论是否是自动提交模式,语句执行后都会生效,区别在于非自动模式下没有提交的那些操作是可以回滚的,一旦提交就不可撤销了。换句话说,当autocommit关闭时,一直是处于事务操作中的,可随时调用ROLLBACK进行回滚。

3.2 MySQL事务隔离级别

​ 事务还会通过锁机制满足隔离性。在InnoDB存储引擎中,有不同的隔离级别,它们有着不同的隔离性。

为什么要设置隔离级别?在数据库操作中,在并发的情况下可能出现如下问题:

  • (1)脏读(Dirty Read):当前事务能够看到别的事务中未提交的数据。A事务读取B事务尚未提交的数据并在此基础上操作,而B事务执行回滚,那么A读取到的数据就是脏数据。

解决办法:如果在第一个事务提交前,任何其他事务不可读取其修改过的值,则可避免该问题。

  • (2)不可重复读(Non-repeatable Reads) :一个事务对同一行数据重复读取两次,但是却得到了不同的结果。事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读该数据时得到与前一次不同的值。

解决办法:如果只有在修改事务完全提交之后才可以读取数据,则可避免该问题。

  • (3)幻读:在其中一个事务中读取到了其他事务新增的数据,仿佛出现了幻影现象。

解决办法:在操作事务完成数据处理之前任何其他事务都不可以添加新数据,则可避免该问题。

正是为了解决以上情况,数据库提供了4种隔离级别,由低到高依次为read uncommitted(读未提交)、read committed(读已提交)、repeatable read(可重复读取)、serializable(序列化)。

  • (1)读未提交:在读未提交这个隔离级别下,即使别的事务所做的修改并未提交,也能看到其修改的数据。当事务的隔离级别处于“读未提交”时,其并发性能是最强的,但是隔离性与安全性是最差的,会出现脏读,在生产环境中不使用。

  • (2)读已提交:读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。该隔离级别避免了脏读,但是却可能出现不可重复读。例如,事务A事先读取了数据,事务B紧接着更新并提交了事务,当事务A再次读取该数据时数据已经发生改变。

  • (3)可重复读:是指在一个事务内多次读同一数据。假设在一个事务还没有结束时,另一个事务也访问同一数据,那么在第一个事务中的两次读数据之间,即使第二个事务对数据进行了修改,第一个事务两次读到的数据也是一样的。这样在一个事务内两次读到的数据就是一样的,因此称为可重复读。读取数据的事务禁止写事务(但允许读事务),写事务则禁止任何其他事务,这样即可避免不可重复读和脏读,但是有时可能出现幻读。

  • (4)序列化:提供严格的事务隔离。它要求事务序列化执行,即事务只能一个接着一个地执行,但不能并发执行。仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。序列化是最高的事务隔离级别,同时代价也最高,性能很低,一般很少使用。在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻读。

隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为read committed。它能够避免脏读,而且具有较好的并发性能。尽管它会导致不可重复读、幻读这些并发问题,但是可以在可能出现这类问题的个别场合采用悲观锁或乐观锁来控制。大多数数据库的默认级别就是read committed,比如SQL Server。MySQL默认设置的隔离级别为REPEATABLE-READ,即“可重复读”。

​ 使用show variables like 'tx_isolation’语句可以查看当前设置的隔离级别,如图3-1所示。

​ 可以通过参数配置MySQL的事务隔离级别,注意不是tx_isolation,而是transaction_isolation。例如,在my.cnf配置文件中设置transaction_isolation=REPEATABLE-READ。也可以使用set语句设置当前会话隔离级别,比如set session transaction isolation level repeatable read。

​ 我们先来总结一下MySQL默认事务隔离级别(可重复读隔离级别)的特性,如图3-2所示,在会话1与会话2中同时开启两个事务,在事务1的事务中修改了t1表的数据以后(将第二条数据的tanme的值修改为ddd),事务2中查看到的数据仍然是事务1修改之前的数据。

​ 如图3-3所示,即使事务1提交了,在事务2没有提交之前,事务2中查看到的数据都是相同的。不管事务1是否提交,在事务2没有提交之前,这条数据对于事务2来说一直都是没有发生改变的,这条数据在事务2中可以被重复地读到。

但是,可能会有一个问题,之前说过,事务的隔离性是由锁来实现的,那么当事务1中执行更新语句时,应该对数据增加了排他锁,但是在事务2中仍然可以进行读操作,为什么呢?这是因为InnoDB采用了“一致性非锁定读”的机制,提高了数据库并发性。一致性非锁定读表示如果当前行被施加了排它锁,那么当需要读取行数据时,则不会等待行上锁的释放,而是会去读取一个快照数据,如图3-4所示。

​ InnoDB中一致性非锁定读的过程之所以称为非锁定读,是因为它不需要等待被访问的行上排他锁的释放。其实快照就是该行所对应的之前版本已提交的数据,即历史数据,快照的实现是由事务日志所对应的undo段来完成的。

3.3 InnoDB的锁机制介绍

​ 锁机制是事务型数据库中为了保证数据的一致性手段。在对MySQL进行性能调优时,往往需要重点考虑锁机制对整个事务的影响。

InnoDB主要使用两种级别的锁机制: 行级锁和表级锁。InnoDB的行级锁类型主要有共享(S)锁(又称读锁)、排他(X)锁(又称写锁)。共享(S)锁允许持有该锁的事务读取行;排他(X)锁允许持有该锁的事务更新或删除行。

​ 如果事务T1在行r上持有共享(S)锁,则其他事务T2对行r的锁的请求按如下方式处理:

  • (1)可以立即授予事务T2对S锁的请求,这样事务T1和T2都在r上持有S锁,但是事务T2的X锁定请求不能立即授予。例如,查询select语句后面增加LOCK IN SHARE MODE,MySQL会对查询结果中的每行都加共享锁,其他事务可以读,但要想申请加排他锁,就会被阻塞。

  • (2)如果事务T1在行r上持有排他(X)锁,则不能立即授予其他事务T2对r上任何类型的锁请求,事务T2必须等待事务T1释放其对行r的锁定。例如,在查询select语句后面增加FOR UPDATE,MySQL会对查询结果中的每行都加排他锁,那么其他任何事务就不能对被锁定的行上加任何锁了,要不然会被阻塞。

​ 在锁机制的实现过程中,为了让行级锁定和表级锁定共存,InnoDB使用了意向锁的概念。意向锁是表级锁,目的是为了防止DDL和DML的并发问题

  • 意向共享锁(IS Lock):事务想要获得一张表中某几行的共享锁,即事务有意向去给一张表中的几行加S锁。

  • 意向排他锁(IX Lock):事务想要获得一张表中某几行的排他锁,即事务有意向去给一张表中的几行加X锁。

​ InnoDB意向锁的存在意义和我们经常说的元数据锁(metadata lock,MDL)很相似。首先,我们看一下所谓的DDL和DML的并发问题,如图3-5所示。

​ 如果没有措施来保证,那么会话2可以直接执行drop语句,这样会话1再执行select就会出错。意向锁的作用和元数据锁的作用类似,就是为了防止在事务进行过程中执行DDL语句的操作而导致数据的不一致。意向锁是当我们有意向给一张表中的几行数据加S读锁时,它会给这张表加意向锁防止DDL的发生。事务1开启了查询,所以获得了元数据锁,事务2要执行DDL,则需获得排他锁,两者互斥,所以事务2需要等待。它们的主要区别就在于意向锁是InnoDB引擎独有的,而元数据锁是Server全局级别锁共有的。MySQL在5.5.3引入了元数据锁,在MySQL 5.7的版本中对其实现方式的算法进行彻底优化,彻底地解决了Server层表锁的性能问题。

​ 根据对数据上锁所锁定的范围不同,InnoDB行级锁种类又可以分为:

  • (1)单个行记录的记录锁(Record Locks):锁定索引中一条记录,比如id=5的记录。

  • (2)区间锁(Gap Locks,又称间隙锁):锁定一个范围。区间锁是锁定索引记录之间的区间,或锁定在第一个或最后一个索引记录之前的区间上。

  • (3)Next-key Lock:锁定一个范围的记录并包含记录本身(上面两者的结合)。Next-key Lock是记录锁与区间锁的组合,当InnoDB扫描索引记录时,会先对选中的索引记录加上记录锁,再对索引记录两边的区间加上区间锁。

​ 我们知道InnoDB支持行级锁,更新同一行数据时会出现锁等待的现象,如果更新不同的行记录,就会成功执行,不会出现锁超时现象。有时虽然更新的是不同行的记录,也会出现锁超时现象,如图3-6所示。因为表t2上的score字段上面没有索引,在第一个会话事务中更新score=70时,其实是把所有的行记录都加了锁,所以第二个会话上更新不同行记录score=80也会报锁超时的错误

InnoDB的行级锁是通过给索引上的索引项加锁来实现的,只有通过索引条件检索数据才使用。在不通过索引条件查询的时候,InnoDB使用的是表级锁,而不是行级锁

​ 在MySQL的Repeatable-Read这个事务级别,为了避免幻读现象,引入了区间锁。它只锁定行记录数据的范围,不包含记录本身,不允许在此范围内插入任何数据。如图3-7所示,表t2有主键,score字段上有索引idc_score,在事务中查询t2表中score<80的记录,在上面加了一个共享锁(lock in sharemode)。

在另外一个事务中,往t2表插入score=74的数据,如图3-8所示,没有插入成功,出现了锁超时,因为在score<80的这个区间内,不允许有任何数据插入,区间锁的功能得到体现;但是插入score=90的数据是成功的,因为不在这个区间内。

3.4 锁等待和死锁

3.4.1 锁等待

​ 锁等待是指一个事务过程中产生的锁,其他事务需要等待上一个事务释放它的锁才能占用该资源。如果该事务一直不释放,就需要持续等待下去,直到超过了锁等待时间,会报一个等待超时的错误。在MySQL中通过innodb_lock_wait_timeout参数来控制锁等待时间,单位是秒。如图3-9所示,可以通过语句show variables like '%innodb_lock_wait%'来查看锁等待超时时间。

​ 当MySQL发生锁等待情况时,可以通过语句select * from sys.innodb_lock_waits \G来在线查看,如图3-10所示,会输出类似的结果。

​ 输出了很多内容,其实最重要的是waiting_pid这个等待事务的线程pid、waiting_query等待锁释放的 语 句 、 blocking_pid 阻 塞 事 务 的 pid 、 blocking_query 阻 塞 事 务 的 SQL 语 句 这 4 个 参 数 。 其 中 ,waiting_pid和blocking_pid两个参数是通过执行show full processlist命令里面输出的线程id号,如图3-11所示。

​ 通过上面两个的输出结果,我们明白了是pid=3的线程锁住了表,造成pid=2的线程的等待。我们看到发生等待的线程对应的SQL语句是“insert into t2 values(4,‘dd’,74)”,但是锁表的线程pid=3对应的SQL语句是NULL。要想找到这个NULL值对应的阻塞语句,可以根据锁表的processlist id 3运用如下SQL语句:

SELECT SQL_TEXT FROM performance_schema.events_statements_current WHERE THREAD_ID in (SELECT THREAD_ID FROM performace_schema threads WHERE PROCESSLIST_ID = 3);

​ 找到NULL对应的SQL语句,如图3-12所示。

3.4.2 死锁

​ 在MySQL中,两个或两个以上的事务相互持有和请求锁,并形成一个循环的依赖关系,就会产生死锁,也就是锁资源请求产生了死循环现象。InnoDB会自动检测事务死锁,立即回滚其中某个事务,并且返回一个错误。它根据某种机制来选择那个最简单(代价最小)的事务来进行回滚。常见的死锁会报错“ERROR 1213 (40001): deadlock found when trying to get lock; try restartingtransaction.”。偶然发生的死锁不必担心,InnoDB存储引擎有一个后台的锁监控线程,该线程负责查看可能的死锁问题,并自动告知用户。

​ 当死锁频繁出现的时候就要引起注意了,可能需要检查应用程序源代码,调整SQL操作顺序,或者缩短事务长度。

​ 在MySQL 5.6版本之前,只有最新的死锁信息可以使用show engine innodb status命令来进行查看。使用Percona Toolkit工具包中的pt-deadlock-logger可以从show engine innodb status的结果中得到指定的时间范围内的死锁信息,同时写入文件或者表中,等待后面的诊断分析。

​ 如果使用的是MySQL 5.6以上版本,可以启用一个新增的参数innodb_print_all_deadlocks(见图3-13),它能把InnoDB中发生的所有死锁信息都记录在错误日志里。

​ 这时可以再去测试一下死锁,错误日志中会出现一条信息:“transactions deadlock detected,dumping detailed information.”。除了这条死锁信息外,还会有更为详细的SQL语句、死锁信息和事务信息。

​ 当两个或多个事务相互持有对方需要的锁时,就会产生死锁,那么如何避免死锁呢?方法如下:

  • (1)不同程序会并发存取多个表或者涉及多行记录时,尽量约定以相同的顺序访问表,可以大大降低死锁的机会。

  • (2)对应用程序进行调整,在某些情况下,通过把大事务分解成多个小事务,使得锁能够更快被释放,及时提交或者回滚事务,可减少死锁发生的概率。

  • (3)在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生的概率。

  • (4)为表添加合理的索引,不用索引将会为表的每一行记录添加上锁,死锁的概率大大增大。

  • (5)对于非常容易产生死锁的业务部分,可以尝试使用升级锁粒度,通过表锁定来减少死锁产生的概率。

3.5 锁问题的监控

​ 监控事务和锁会对数据库的运行状态有更加全面的认识,在数据库出现异常时也可以很快定位到一些问题。例如,业务设计开发人员开启了事务但是忘了提交,或者事务提交时间过长,都会导致一些数据库的问题产生,严重时会引起数据库故障。下面就如何查看和监控事务、锁信息做一个简单介绍。

​ 我们通过show full processlist是为了查看当前MySQL是否有压力、都在跑什么语句、当前语句耗时多久了,从中可以看到总共有多少链接数、哪些线程有问题,然后把有问题的线程kill掉,临时解决一些突发性的问题。

​ 通过执行show engine innodb status命令来查看是否存在锁表情况,如图3-14所示。

​ MySQL将事务和锁信息记录在了information_schema数据库中,我们只需要查询即可。涉及的表主要有3个,即innodb_trx、innodb_locks、innodb_lock_waits,可以帮我们方便地监控当前的事务并分析可能存在的锁问题。

​ 通过information_schema.innodb_trx表查看事务情况,结果如图3-15所示。这张表的主要字段有:

  • trx_id:唯一的事务id号。
  • trx_state:当前事务的状态,本例中7969事务号是lock_wait锁等待状态。
  • trx_wait_started:事务开始等待的时间。
  • trx_mysql_thread_id:线程id与show full processlist相对应。
  • Trx_query:事务运行的SQL语句。
  • trx_operation_state:事务运行的状态。

​ 通过information_schema.INNODB_LOCKS表查询锁情况,如图3-16所示。INNODB_LOCKS表主要包含了InnoDB事务锁的具体情况,包括事务正在申请加的锁和事务加上的锁。

​ 通 过 information_schema.INNODB_LOCK_waits 查 看 锁 阻 塞 情 况 , 如 图 3-17 所 示 , 字 段
requesting_trx_id请求锁的事务ID(等待方),字段blocking_trx_id阻塞该锁的事务ID(当前持有方,待释放)。

第4章 SQL语句性能优化【*】

​ 说起SQL语句性能优化,相信所有人都了解一些简单的技巧:不使用SELECT *、不使用NULL字段、合理地使用索引、为字段选择恰当的数据类型等。你是否真的理解这些优化技巧?是否理解其背后的工作原理?本章从理论和实战角度出发,讲解这些优化建议背后的原理。

4.1 MySQL查询过程

​ 很多SQL查询优化工作实际上就是遵循一些原则让MySQL的优化器能够按照预想的合理方式运行而已。所以,如果希望MySQL能够获得更快的查询性能,那么最好的方法就是搞明白MySQL是如何优化和执行SQL查询的。

​ 当向MySQL发送一个SQL请求的时候,MySQL的查询执行过程如图4-1所示。

​ 客户端用一个单独的数据包将查询请求发送给服务器,所以当查询语句很长的时候,需要设置max_allowed_packet参数,即服务器端和客户端在一次传送数据包的过程中数据包的大小(最大限制),如果超出这个值,服务端会拒绝接收更多数据并抛出异常。然而,服务器响应给用户的数据通常会很多, 是由多个数据包组成的。当服务器响应客户端请求时,客户端必须完整地接收整个返回结果,而不能只是 简单地取出前面几条结果,然后让服务器停止发送。所以,在开发过程中,*要保持查询尽量简单而且只返回必需的数据。为了减小通信间数据包的大小和数量、降低不必要的I/O,一个优秀的习惯是在查询中尽量避免使用SELECT 以及加上LIMIT限制

​ 在解析一个查询语句前,如果查询缓存是打开的,那么MySQL会检查这个查询语句是否命中查询缓存中的数据。查询缓存通过Query Cache进行操作,如果当前查询恰好命中查询缓存,就在检查一次用户权限后直接返回缓存中的结果。Query Cache和Buffer Pool缓存机制有很大的区别。Query Cache缓存的是SQL语句及对应的结果集,缓存在内存,最简单的情况是SQL语句一直不重复,那么Query Cache的命令率肯定是0。Buffer Pool中缓存的是整张表中的数据,缓存在内存,即使SQL语句再变,只要数据都在内存,那么命中率就是100%。另外两个查询在任何字符上稍微有一点不同(例如:空格、注释),都会导致Query Cache 查 询 缓 存 不 会 命 中 。 而且查 询语句中包含任何不确定的函数(比如now()、current_date()),其查询结果都不会被缓存。

​ **既然是缓存,那么查询缓存何时失效呢?**对于Query Cache这个查询缓存来说,如果数据表被更改,那么和这个数据表相关的全部Cache都会无效并删除。如果你的数据表更新频繁的话,MySQL的查询缓存系统会跟踪查询中涉及的每个表,对于密集写操作,启用查询缓存后很可能造成频繁的缓存失效,间接引发内存激增及CPU飙升,对已经非常忙碌的数据库系统是一种极大的负担。所以在生产环境中通常建议将Query Cache全面禁用。

解析器通过关键字将SQL语句进行解析,并生成对应的解析树。MySQL解析器将使用MySQL语法规则验证和解析查询。预处理则根据一些MySQL规则进一步检查解析树是否合法,例如检查数据表和数据列是否存在,还会解析名字和别名,看看它们是否有歧义。

查询优化器会将解析树转化成执行计划。一条查询可以有多种执行方法,最后都是返回相同结果。优化器的作用就是分析出最优化数据检索的方式,找到其中最好的执行计划。MySQL使用基于成本的优化器,尝试预测一个查询使用某种执行计划时的成本,并选择其中成本最小的一个。MySQL计算当前查询的成本,是根据一些列的统计信息计算得来的,这些统计信息包括每张表或者索引的页面个数、索引的基数、索引和数据行的长度、索引的分布情况等。有非常多的原因会导致MySQL选择错误的执行计划,比如统计信息不准确。需要注意的是,MySQL只选择它认为成本小的为最优,但是成本小并不意味着执行时间短

​ 生成执行计划的过程会消耗较多的时间,特别是存在许多可选的执行计划时。如果在一条SQL语句执行的过程中,将该语句对应的最终执行计划进行缓存,当相似的语句再次被输入服务器时,就可以直接使用已缓存的执行计划,从而跳过SQL语句生成执行计划的整个过程,进而提高语句的执行速度。

​ 现在,让我们总结一下MySQL整个查询执行过程,总的来说分为5个步骤:

  • (1)客户端向MySQL服务器发送一条查询请求。

  • (2)服务器首先检查查询缓存,如果命中缓存,则立刻返回存储在缓存中的结果,否则进入下一阶段。

  • (3)服务器进行SQL解析、预处理,再由优化器生成对应的执行计划。

  • (4)MySQL根据执行计划,调用存储引擎的API来执行查询。

  • (5)查询执行的最后一个阶段是将结果返回给客户端。即使查询不需要返回结果给客户端,MySQL仍然会返回这个查询的一些信息,如该查询影响到的行数。

4.2 创建高性能索引【*】

4.2.1 索引的原理

索引是提高MySQL查询性能的一个重要途径。应当尽量避免事后才想起添加索引,因为事后可能需要监控大量的SQL才能定位到问题所在,而且增加索引的时间肯定是远大于初始增加索引所需要的时间。

我们使用索引,就是为了提高查询的效率,如同查字典一样,索引的本质就是不断缩小获取数据的筛选范围,同时把随机的事件变成顺序的事件,找出我们想要锁定的数据。

​ 我们先对索引结构进行了解。数据库中实际使用的索引并不会是链表结构,因为效率太低了。数据库中的索引使用的是树形结构

​ 如果二叉查找树可以任意构造,那么同样是2、3、5、6、7、8这6个数字,按照图4-2的方式来构造的话,查询效率就太低了。

​ 若想二叉树的查询效率尽可能高,需要这棵二叉树是平衡的,即平衡二叉树,如图4-3所示。所有节点至多拥有两个子节点,节点左指针指向小于其关键字的子树,右指针指向大于其关键字的子树。

​ 索引往往以索引文件的形式存储在磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗,相对于内存存取,I/O存取的消耗要高几个数量级。如果索引是采用平衡二叉树的结构,可以想象一下一棵几百万节点的二叉树,每读取一个节点,需要一次磁盘的I/O读取,整个查找的耗时显然是不能够接受的。每次读取的磁盘页的数据中有许多是用不上的。因此,查找过程中要进行许多次的磁盘读取操作。适合作为索引的结构应该是尽可能少地执行磁盘I/O操作,因为执行磁盘I/O操作是非常耗时的。因此,平衡二叉树还不是很适合作为索引结构。减少磁盘I/O的次数就必须压缩树的高度,让瘦高的树尽量变成矮胖的树。

​ BTree是为了充分利用磁盘预读功能而创建的一种数据结构。BTree相对于平衡二叉树缩减了节点个数,使每次磁盘I/O取到内存的数据都发挥了作用,从而提高了查询效率。

B+Tree是在BTree基础上的一种优化,其更适合实现外存储索引结构,MySQL的InnoDB存储引擎就是用B+Tree实现其索引结构的。B+Tree比BTree更适合作为索引的结构,它是BTree的变种,是基于BTree来改进的。数据库索引采用B+Tree的主要原因是BTree在提高了磁盘I/O性能的同时,并没有解决元素遍历的效率低下的问题。B+Tree也是一种多路搜索树,只要遍历叶子节点就可以实现整棵树的遍历,B+Tree的结构也特别适合带有范围的查找。

与BTree相比,B+Tree有以下不同点:非叶子节点不存储data,只存储索引key;只有叶子节点才存储data。B+Tree的结构如图4-4所示。

​ 理解B+Tree时,只需要理解其最重要的两个特征即可:第一,所有的关键字(可以理解为数据)都存储在叶子节点(Leaf Page),非叶子节点(Index Page)并不存储真正的数据,所有记录节点都是按键值大小顺序存放在同一层叶子节点上其次,所有的叶子节点由指针连接

​ MySQL中B+Tree在经典B+Tree的基础上进一步做了优化,增加了顺序访问指针。如图4-5所示,在B+Tree的每个叶子节点增加一个指向相邻叶子节点的指针,就形成了带有顺序访问指针的B+Tree。如果要查询key为从15到49的所有数据记录,当找到15后,只需顺着节点和指针顺序遍历就可以一次性访问到所有数据节点,这样就提高了区间访问性能(无须返回上层父节点重复遍历查找减少I/O操作)。

B+Tree结构尽量减少查找过程中磁盘I/O的存取次数,只在叶子节点存储数据;所有叶子结点包含一个链指针;其他内层非叶子节点只存储索引数据;只利用索引快速定位数据索引范围,先定位索引再通过索引高效、快速定位数据

为表设置索引是要付出代价的:一是增加了数据库的存储空间,二是在插入和修改数据时要花费较多的时间(因为索引也要随之变动)

4.2.2 聚集索引和辅助索引

数据库中的B+Tree索引可以分为聚集索引(clustered index)和辅助索引(secondary index)聚集索引与辅助索引的不同在于叶子结点存放的是否是一整行的记录数据辅助索引的叶子节点并不包含行记录的全部数据,而是存储相应行数据的聚集索引键,即主键。当通过辅助索引来查询数据时,InnoDB存储引擎会遍历辅助索引找到主键,然后通过主键在聚集索引中找到完整的行记录数据(这个过程称为:回表)。

聚集索引其实是一种索引组织形式,索引键值的逻辑顺序决定了表数据行的物理存储顺序。InnoDB存储引擎表是索引组织表,即表中数据按主键顺序存放。聚集索引叶节点存放整张表的行记录数据,每张表只能有一个聚集索引。

​ 对于InnoDB存储引擎,主键毫无疑问是一个聚集索引。如果一个主键被定义了,那么这个主键就作为聚集索引。如果没有主键被定义,那么InnoDB取第一个唯一索引(unique)而且只含非空列(NOT NULL)作为主键,InnoDB使用它作为聚集索引。如果没有这样的列,InnoDB就自己产生一个ID值,它有6个字节,而且是隐藏的,使其作为聚集索引。如果表使用自增列(INT/BIGINT类型)做主键,这时写入顺序是自增的,和B+树叶子节点分裂顺序一致,避免了插入过程中的聚集索引排序问题。如果使用非自增主键(如身份证号或学号等),由于每次插入主键的值近似于随机,因此每次新记录都要被插到现有索引页的中间某个位置,此时MySQL不得不为了将新记录插到合适位置而移动数据,而频繁的移动、分页操作会造成大量的碎片,增加很多开销。

​ 除了聚集索引外,表中其他索引都是辅助索引(Secondary Index,二级索引,或称为非聚集索引),与聚集索引的区别是:辅助索引的叶子节点不包含行记录的全部数据。叶子节点除了包含键值以外,每个叶子节点中的索引行中还包含一个书签(bookmark)。该书签用来告诉InnoDB存储引擎去哪里可以找到与索引相对应的行数据。

​ 辅助索引的存在并不影响数据在聚集索引中的组织,因此每张表上可以有多个辅助索引,但只能有一个聚集索引。当通过辅助索引来寻找数据时,InnoDB存储引擎会遍历辅助索引并通过叶子级别的指针获得相应的主键索引的主键,然后通过主键索引来找到一个完整的行记录。

4.2.3 Index Condition Pushdown(索引下推)【*】

​ Index Condition Pushdown(简称ICP)是MySQL使用索引从表中检索行数据的一种优化方式。ICP的目标是减少从基表中读取操作的数量,从而降低I/O操作。禁用ICP,存储引擎会通过遍历索引定位基表中的行,然后返回给Server层,再去为这些数据行进行WHERE后的条件过滤。现在开启ICP这个特性,如果部分WHERE条件能使用索引中的字段,MySQL Server就会把这部分下推到存储引擎层。存储引擎通过索引过滤,把满足的行从表中读取出ICP能减少引擎层访问基表的次数和MySQL Server访问存储引擎的次数。总之,ICP的加速效果取决于在存储引擎内通过ICP筛选掉的数据的比例。如果引擎层能够过滤掉大量的数据,就能减少I/O次数、提高查询语句性能。

​ 对于InnoDB表,ICP只适用于辅助索引,当使用ICP优化时,执行计划的Extra列显示Using indexcondition 提 示 。 MySQL 开 启 ICP 的 方 法 也 很 简 单 : SET optimizer_switch=“index_condition_pushdown=on”。

​ 下面使用场景举例说明ICP原理。比如People表有一个二级索引INDEX (zipcode, lastname,firstname),用户只知道某用户的zipcode和大概的lastname、address,却想查询相关信息。

​ 若不使用ICP,则是通过二级索引中zipcode的值去基表取出所有zipcode='350001’的数据,然后Server层再对“lastname LIKE '%jerry%'AND address LIKE ‘%Main Street%’;”进行过滤:

SELECT * FROM people WHERE zipcode='350001' AND lastname LIKE '%etrunia%' AND address LIKE '%Main Street%';

实验举例

​ 一张表默认只有一个主索引,因为ICP只能作用于二级索引,所以我们建立一个二级索引,语句为“ALTER TABLE employees ADD INDEX first_name_last_name (first_name, last_name) ”,如图4-6所示。这样就建立了一个first_name和last_name的联合索引。

​ 为了明确看到查询性能,启用profiling并关闭Query Cache,如图4-7所示。

​ 在“SELECT * FROM employees WHERE first_name=‘Mary’ AND last_name LIKE ‘%man’ ”中查询,根据MySQL索引的前缀匹配原则,两者对索引的使用是一致的,即只有first_name采用索引,last_name由于使用了模糊前缀,无法使用索引进行匹配。将查询执行3次,执行语句show profiles,结果如图4-8所示。

​ 查看执行计划(查询SQL的查询执行计划,在select语句前加上EXPLAIN即可):explain SELECT *FROM employees WHERE first_name=‘Mary’ AND last_name LIKE ‘%man’,如图4-9所示。

​ 关闭ICP(ICP禁用),执行SET optimizer_switch='index_condition_pushdown=off’语句,再运行3次相同的查询,结果如图4-10所示。

​ 禁用ICP后,同样的查询,耗时是之前的两倍多。下面我们用explain看看后者的执行计划:explain SELECT * FROM employees WHERE first_name=‘Mary’ AND last_name LIKE ‘%man’,如图4-11所示。

​ 从开启ICP和禁用ICP的执行计划可以看到区别在于Extra列:开启ICP时,用的是Using index condition;关闭ICP时,Extra列是Using where。

4.2.4 Multi-Range Read Optimization

​ Multi-Range Read Optimization(简称MRR)是优化器将随机I/O转化为顺序I/O,目的是减少磁盘的随机访问,以降低查询过程中I/O的开销,对I/O-bound类型的SQL语句性能带来极大的提升。

​ 如图4-12所示,在不使用MRR时,优化器需要根据二级索引返回的记录来进行“回表”,这个过程一般会有较多的随机I/O。

​ 如图4-13所示,在使用MRR时,MRR的优化在于,并不是每次通过辅助索引回表取记录,而是将其rowid缓存起来,然后对rowid进行排序后再去访问记录,优化器将二级索引随机的I/O进行排序,转化为主键的有序排列,从而实现随机I/O到顺序I/O的转化,大幅提升性能。

​ 对比一下mrr=on & mrr=off时的执行计划,其中测试表t1的表结构如图4-14所示。

​ 如图4-15所示,在关掉MRR的情况下,执行计划使用的是索引xx©,即从索引xx上读取一条数据后回表,取回该主键的完整数据,当数据较多且比较分散的情况下会有比较多的随机I/O,导致性能低下。

​ 在MySQL当前版本中,基于成本的算法过于保守,导致大部分情况下优化器都不会选择MRR特性。为了确保优化器使用MRR特性,需要执行SQL语句:“set optimizer_switch=‘mrr=on,mrr_cost_based=off’”,如图4-16所示。可以看到extra的输出中多了Using MRR信息,即使用MRR Optimization I/O层面进行了优化,可以减少I/O方面的开销。

​ 测试用例都是在mrr_cost_based=OFF的情况下进行的,因为SQL语句是否使用MRR优化依赖于其代价的大小(优化器的代价计算是一个比较复杂的过程,无论是MRR还是BKA都只是优化器进行优化的方法),当其发现优 化后的代价过高时就会不使用该项优化。因此在使用MRR相关的优化时,尽量设置mrr_cost_based=ON,毕竟大多数情况下优化器是对的。

在不使用MRR之前,先根据where条件中的辅助索引获取辅助索引与主键的集合,再通过主键来获取对应的值。利用辅助索引获取的主键来访问表中的数据会导致随机的I/O(辅助索引的存储顺序并非与主键的顺序一致),随机主键不在同一个page里时会导致多次I/O和随机读。使用MRR优化的好处是能使数据访问变得较有顺序。它将根据辅助索引获取的结果集根据主键进行排序,将无序化为有序,可以用主键顺序访问基表,将随机读转化为顺序读,多页数据记录可一次性读入或根据此次的主键范围分次读入,减少I/O操作,提高查询效率

​ 相关参数如下:

  • mrr=on,mrr_cost_based=on:表示cost base的方式还选择启用MRR优化,当发现优化后的代价过高时就会不使用该项优化。

  • mrr=on,mrr_cost_based=off:表示总是开启MRR优化。

4.2.5 Batched Key Access

​ 直白地说,Batched Key Access(BKA)是提高表join性能的算法是在表连接的过程中为了提升join性能而使用的一种join buffer,作用是在读取被连接表的记录时使用顺序I/O

​ 对于嵌套循环(Nested Loop),如果关联的表数据量很大,那么join关联的时间会很长。后来MySQL引入了BNL(Block Nested Loop)算法来优化嵌套循环。BNL算法通过使用在外部循环中读取行的缓冲来减少内部循环中的表必须被读取的次数。

​ BNL算法原理将外层循环的行/结果集存入join buffer,内存循环的每一行数据与整个buffer中的记录做比较,以减少内层循环的扫描次数。举个简单的例子:外层循环结果集有1000行数据,如果使用BNL算法,则先取出外层表结果集的100行存放到join buffer缓冲区,然后用内层表的每一行数据去和缓冲区中的100行结果集做比较,可以一次性与100行数据进行比较,这样内层表其实只需要循环1000/100=10次,减少了9/10。

BKA的原理是对于多表join语句,将外部表中相关的列放入join buffer中。批量地将Key(索引键值)发送到Multi-Range Read(MRR)接口。Multi-Range Read(MRR)根据收到的Key对应的ROWID进行排序,然后进行数据的读取操作。

​ BKA join算法将能极大地提高SQL的执行效率,特别是在内部表上有索引并且该索引为非主键,联接需要访问内部表主键上的索引的情况下。这时BKA算法会调用Multi-Range Read(MRR)接口,批量地进行索引键的匹配和主键索引上获取数据的操作,以此来提高联接的执行效率,因为读取数据是以顺序磁盘I/O而不是随机磁盘I/O进行的。

​ BKA使用join buffer size来确定buffer的大小,buffer越大缓冲区越大,对联接操作的右侧表的顺序访问就越多,可以显著提高性能。

​ BNL比BKA出现得早,BKA直到MySQL 5.6才出现。BNL主要用于被join的表上无索引,而BKA主要是指在被join表上有索引可以利用,那么就在行提交给被join的表之前,对这些行按照索引字段进行排序,因此减少了随机I/O。如果被join的表上没有索引,则使用老版本的BNL策略。

​ 我们看一个演示例子,还是用MySQL的一个示例数据库,可以在GitHub上下载(下载地址:https://github.com/datacharmer/test_db ) 。 要 使 用 BKA , 必 须 将 optimizer_switch 系 统 变 量 的batched_key_access标志设置为on。 BKA使用MRR,因此MRR功能也必须打开。MRR基于mrr_cost_based的成本估算并不能保证总是使用MRR,因此mrr_cost_based也必须关闭才能使用BKA。

​ 以下语句设置启用BKA:

mysql> set optimizer_switch="mrr=on,mrr_cost_based=off, batched_key_access=on"

​ 如图4-17所示,启用BKA,用explain查看执行计划,在执行计划的extra信息中看到的是using join buffer(Block Nested Loop)。

​ 这时添加一个索引,如图4-18所示,在EXPLAIN输出中,当Extra值包含Using join buffer(BatchedKey Access)且类型值为ref或eq_ref时,表示使用BKA。

​ 除了使用optimizer_switch系统变量来控制优化程序在会话范围内使用BNL和BKA算法之外,MySQL还支持优化程序提示hint,如图4-19所示。

​ 使用hint,强制用BKA的方法语句使用示例:

 mysql> explain SELECT /*+ bka(a)*/ a.gender, b.dept_no FROM employees a, dept_emp b WHERE a.birth_date = b.from_date;

4.3 慢SQL语句优化思路

​ 对慢SQL语句优化一般可以按下面几步思路:

  • 开启慢查询日志,设置超过几秒为慢SQL语句,抓取慢SQL语句;
  • 通过explain查看执行计划,对慢SQL语句分析;
  • 创建索引并调整语句,再查看执行计划,对比调优结果。

4.3.1 抓取慢SQL语句

​ 索引是提高MySQL查询性能的一个重要途径。应当尽量避免事后才想起添加索引,因为事后可能需要监控大量的SQL才能定位到问题所在,而且增加索引的时间肯定远大于初始增加索引所需要的时间

​ 某些SQL语句执行完毕所花费的时间特别长,我们将这种响应比较慢的语句记录在慢查询日志中。不要被“查询日志”的名字误导,错误地以为慢查询日志只会记录执行比较慢的select语句,其实不然,insert、delete、update、call等DML操作只要是超过了指定的时间,都可以称为“慢查询”,都会被记录在慢查询日志中。默认情况下,慢查询日志是不被开启的,如果需要,可以手动开启,开启慢查询日志之后,默认设置下执行超过10秒的语句才会被记录到慢查询日志中。当然,超过多长时间才是我们认为的“慢”,可以自定义这个阈值。

​ 先来看看跟慢查询日志相关的常用参数,如图4-20所示。

  • 参数slow_query_log :表示是否开启慢查询日志。语句“set global slow_query_log=on”临时开启慢查询日志,如果想关闭慢查询日志只需要执行“set global slow_query_log=off ”即可。
  • 参数slow_query_log_file:当使用文件存储慢查询日志时(log_output设置为“FILE”或者“FILE,TABLE”时),指定慢查询日志存储于哪个日志文件中,默认的慢查询日志文件名为“主机名-slow.log”,慢查询日志的位置为datadir参数所对应的目录位置。另外,在MySQL 5.7.2之后,如果设置了慢日志是写到文件里,就需要设置log_timestamps(默认是UTC时间,比我们晚8小时,需要设置为系统时间log_timestamps=SYSTEM)来控制写入到慢日志文件里面的时区(该参数同时影响general日志和error日志)。
  • 参数long_query_time :表示“多长时间的查询”被认定为“慢查询”,默认值为10秒,表示超过10秒的查询被认定为慢查询。语句“set long_query_time=5”表示现在起所有执行时间超过1秒的SQL都将被记录到慢查询文件中。
  • 参数log_queries_not_using_indexes :表示如果运行的SQL语句没有使用到索引,是否也被当作慢查询语句记录到慢查询日志中,OFF表示不记录,ON表示记录。
  • 参数log_throttle_queries_not_using_indexes :当log_queries_not_using_indexes设置为ON时 ,没有使用索引的查询语句也会被当作慢查询语句记录到慢查询日志中。使用log_throttle_queries_not_using_indexes可以限制这种语句每分钟记录到慢查询日志中的次数, 因为在生产环境中有可能有很多没有使用索引的语句,此类语句频繁地被记录到慢查询日志中,可能会导致慢查询日志快速不断地增长,管理员可以通过此参数进行控制。

​ 慢查询日志中给出了账号、主机、运行时间、锁定时间、返回行等信息,然后根据这些信息来分析此SQL语句哪里出了问题。当开始使用慢查询功能后,可能随着慢查询日志越来越大,通过vi或cat命令不能很直观地查看慢查询日志,这时就可以使用MySQL内置的mysqldumpslow命令来进行分析。

​ 如图4-21所示,使用mysqldumpslow进行分析,命令为mysqldumpslow -t 10 /data/mysql/mysql-slow.log ,作用是显示出慢查询日志中最慢的10条SQL。

​ mysqldumpslow命令的主要参数如下:

  • -s表示按照何种方式排序,c、t、l、r分别是按照记录次数、时间、查询时间、返回的记录数来排序,ac、at、al、ar表示相应的倒叙。
  • -t是top n的意思,即返回前面多少条数据。
  • -g后边可以写一个正则匹配模式,大小写不敏感的。

​ 例如,mysqldumpslow -s r -t 20 /database/mysql/slow-log表示为得到返回记录集最多的20个查询;mysqldumpslow -s t -t 20 -g “left join” /database/mysql/slow-log表示得到按照时间排序的前20条里面含有左连接的查询语句。

​ mysqldumpslow是MySQL安装后就自带的工具,用于分析慢查询日志;但是pt-query-digest却不是MySQL自带的。percona-toolkit工具是DBA运维工作的强大武器,安装后有一组命令,其中pt-query-digest命令捕获线上SQL语句,对其进行分析,生成慢查询日志的分析报告。

​ pt-query-digest查询出来的结果分为3部分,如图4-22所示。第一部分显示日志的时间范围,以及总的SQL数量和不同的SQL数量;第二部分是SQL语句的一个占比结果显示,如总的响应时间Response、该查询在本次分析中总的时间占比time、执行次数Calls(本次分析总共有多少条这种类型的查询语句)、平均每次执行的响应时间R/Call、查询对象Item(具体SQL语句);第三部分是每一个SQL具体的分析结果。

​ 查询次数多且每次查询占用时间长的SQL通常为pt-query-digest分析的前几个查询。注意查看pt-query-digest分析中的Rows examine项,可以找出I/O消耗大的SQL。注意查看pt-query-digest分析中Rows examine(扫描行数)和 Rows sent (发送行数)的对比 ,如果扫描行数远远大于发送行数,就说明索引命中率并不高。

4.3.2 利用explain分析查询语句【*】

​ 在工作中,我们用于捕捉性能问题最常用的就是打开慢查询,定位执行效率差的SQL。当我们定位到一个SQL以后还不算完事,我们还需要知道该SQL的执行计划,比如是全表扫描还是索引扫描,这些都需要通过explain去完成。explain命令是查看优化器如何决定执行查询的主要方法,从而知道MySQL如何处理SQL语句以及查询语句是否走了合理的索引。

​ 使用explain,只需要在查询中的select关键字之前增加explain这个词即可,MySQL会在查询上设置一个标记,当执行查询时返回关于在执行计划中每一步的信息,而不是执行它。通过explain输出的内容如图4-23所示。

  • (1)id:反映的是表的读取顺序或查询中执行select子句的顺序。

    • ① id相同,执行顺序是由上至下的。
    • ② id不同,如果是子查询,id序号会递增,id值越大优先级越高,越先被执行。
    • ③ id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行。
  • (2)select_type:表示select的类型,主要用于区别普通查询、联合查询、子查询等复杂查询。

    • ① simple:简单的select查询,查询中不包含子查询或union。
    • ② primary:查询中若包含任何复杂的子部分,最外层查询标记为primary。
    • ③ subquery:select或where列表中的子查询。
    • ④ derived(衍生):在from列表中包含的子查询,MySQL会递归执行这些子查询,把结果放在临时表里。
    • ⑤ union:若第二个select出现在union后,则被标记为union;若union包含在from子句的子查询中,外层select将被标记为derived。
    • ⑥ union result:union后的结果集。
  • (3)table:显示这一步所访问数据库中表名称(显示这一行的数据是关于哪张表的),有时不是真实的表名字,可能是第几步执行的结果的简称。

  • (4)type:对表的访问方式,表示MySQL在表中找到所需行的方式,又称“访问类型”。常见的访问类型有ALL、index、range、 ref、eq_ref、const、system、NULL(从左到右,性能从差到好)。

    • ① ALL:Full Table Scan,MySQL将遍历全表以找到匹配的行。
    • ② index::Full Index Scan,index与ALL的区别为index类型只遍历索引树。
    • ③ range:索引范围扫描,返回一批只检索给定范围的行,使用一个索引来选择行,一般就是在where语句中出现between、< 、>、in等的查询。这种范围扫描索引比全表扫描要好,因为它只需要开始于索引的某一点,而结束于另一点,不用扫描全部索引。
    • ④ ref:非唯一性索引扫描,返回匹配某个单独值的所有行,本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而它可能会找到多个符合条件的行,所以应该属于查找和扫描的混合体。
    • ⑤ eq_ref:类似ref,区别在于使用的索引是唯一索引,对于每个索引键,表中只有一条记录与之匹配,常见于主键或唯一索引扫描。简单来说,就是多表连接中使用primary key或者unique key作为关联条件。
    • ⑥ const、system:当MySQL对查询某部分进行优化并转换为一个常量时,使用这些类型访问。如果查询条件用到常量,那么通过索引一次就能找到,常在使用primary key或unique的索引中出现。system是const类型的特例,当查询的表只有一行的情况下使用。
    • ⑦ NULL:MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成。
  • (5)possible_keys:指出MySQL能使用哪个索引在该表中找到行,查询涉及的字段上若存在索引,则该索引将被列出,但不一定会被查询使用。

  • (6)key:显示MySQL实际决定使用的索引,如果没有选择索引,则显示是NULL。要想强制MySQL使用或忽视possible_keys列中的索引,在查询中使用FORCE INDEX或者IGNORE INDEX。查询中若使用了覆盖索引(select后要查询的字段刚好和创建的索引字段完全相同),则该索引仅出现在key列表中。

  • (7)key_len:显示索引中使用的字节数。

  • (8)ref:表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值。

  • (9)rows:显示MySQL根据表统计信息以及索引选用的情况,估算找到所需的记录要读取的行数。

  • (10)Extra:该列包含MySQL解决查询的说明和描述,包含不适合在其他列中显示但是对执行计划非常重要的额外信息。

    • ① Using where:不用读取表中所有信息,仅通过索引就可以获取所需数据,发生在对表的全部请求列都是同一个索引部分的时候,表示MySQL服务器将在存储引擎检索行后再进行过滤。
    • ② Using temporary:表示MySQL需要使用临时表来存储结果集,MySQL在对查询结果排序时使用临时表,常见于排序(order by)和分组查询(group by)。
    • ③ Using filesort:当Query中包含order by操作而且无法利用索引完成的排序操作称为“文件排序”,创建索引时会对数据先进行排序,出现using filesort一般是因为order by后的条件导致索引失效,最好进行优化。
    • ④ Using join buffer:表明使用了连接缓存,比如说在查询的时候,多表join的次数非常多,就将配置文件中缓冲区的join buffer调大一些。如果出现了这个值,应该注意,根据查询的具体情况可能需要添加索引来改进。
    • ⑤ Using index:只使用索引树中的信息,而不需要进一步搜索读取实际的行来检索表中的列信息。相应的select操作中使用了覆盖索引,避免访问了表的数据行,效率好。覆盖索引:select后的数据列只从索引就能取得,不必读取数据行,且与所建索引的个数(查询列小于等于索引个数)、顺序一致。如果要用覆盖索引,就要注意select的列只取需要用到的列,不用select *,同时如果将所有字段一起做索引会导致索引文件过大,性能会下降。
    • ⑥ Using Index Condition:表示进行了ICP优化。

总结一下针对explain命令生成执行计划:

  • 首先关注查询类型type列,如果出现all关键字,代表全表扫描,没有用到任何index;
  • 再看key列,如果key列是NULL,代表没有使用索引;
  • 然后看rows列,该列数值越大意味着需要扫描的行数越多,相应耗时越长;
  • 最后看Extra列,要避免出现Using filesort或Using temporary这样的字眼,这是很影响性能的。

4.3.3 利用show profiles分析慢SQL语句

​ show profile也是分析慢SQL语句的一种手段,通过它可以分析出一条SQL语句的性能瓶颈在什么地方。它可以定位出一条SQL语句执行的各种资源消耗情况,比如CPU、I/O等,以及该SQL执行所耗费的时间等。

​ 如图4-24所示,开启后,查看开启状态,15表示历史缓存SQL的个数。

​ 通过show profiles查看各语句执行时间,如图4-25所示。

​ 如图4-26所示,通过Query_ID可以得到具体SQL连接、服务、引擎、存储4层结构完整生命周期的耗时。

​ 如图4-27所示,分析指定的SQL语句(第一行的165是上面查出来的Query_ID,可以从中查出一条SQL语句执行的各种资源消耗情况,比如CPU、I/O等)。

4.4 索引使用的原则及案例分析【*】

4.4.1 索引使用的原则

  • (1)表一定要有主键,显式定义主键且采用与业务无关的列以避免修改。InnoDB表在有主键时会自动将主键设为聚集索引,建议采用自增列来使数据顺序插入。
  • (2)关于合理添加索引,有一个通常的法则,即对于经常被查询的列、经常用于表连接的列、经常排序分组的列,需要创建索引。
  • (3)创建索引之前,还要查看索引的选择性(不重复的索引值和表的记录总数的比值)来判断这个字段是否合适创建索引。索引的选择性越接近于1,说明选择性越高,非常适合创建索引(即:索引区分度,索引区分度 = count(distint 记录) / count(记录))。
  • (4)组合索引(表中两个或两个以上的列上创建的索引),一般把选择性高的列放在前面。组合索引字段数不建议超过5个,如果5个字段还不能极大地缩小row范围,那么肯定是设计有问题。
  • (5)合理利用覆盖索引(只需通过索引就可以返回查询所需要的数据,不必在查到索引之后再回表查询数据)。禁止使用select *,只获取必要字段,指定字段能有效利用索引覆盖。
  • (6)使用explain判断SQL语句是否合理使用了索引,尽量避免Extra列出现Using File Sort、Using Temporary。
  • (7)单张表的索引数量建议控制在5个以内,索引太多也会浪费空间且降低修改数据的速度,影响性能。
  • (8)不建议在频繁更新的字段上建立索引。
  • (9)Where条件中的索引列不能是表达式的一部分,避免在Where条件中在索引列上进行计算或使用函数,因为这将导致索引不被使用而进行全表扫描。
  • (10)如果要进行join查询,那么被join的字段必须类型相同并建立索引,因为join字段类型不一致会导致全表扫描。
  • (11)隐式类型转换会使索引失效,导致全表扫描。

4.4.2 没有使用到索引的案例分析

​ 以下演示违反索引使用规则的情形,会导致全表扫描。

(1)案例一:在表tt1中,给字段log_time增加了索引,如图4-28所示。

​ 通过explain查看查询SELECT * FROM t1 WHERE DATE(log_time)='2015-04-09’的执行计划,如图4-29所示。我们发现type列是ALL,这条语句进行了一个全表扫描。虽然给字段log_time加了索引,但是没有用到索引,因为违背避免在Where条件中在索引列上进行计算或使用函数。在MySQL里,一般修改为SELECT * FROM t1 WHERE log_time >= ‘2015-04-09 00:00:00’ AND log_time <= ‘2015-04-10 00:00:00’,通过explain查看查询执行计划时使用到了索引。MySQL 5.7的虚拟列(Generated Columns)特性实现表达式索引,就是用来解决这个问题的。可以增加一个可被索引的列,但实际上并不存在于数据表中。可以对log_time创建一个虚拟列,然后对虚拟列创建索引,但是后期会增加运维人员的运维难度。

(2)案例二:在表test_2中,SQL语句非常简单,就是“select id,user_id,name from test_1 where user_id=1”这种类型,而且user_id上已经建立索引,查询还是很慢,如图4-30所示。利用explain查看语句执行计划,发现进行了全表扫描,type列还是ALL,并没有用上user_id的索引。

​ 认真观察一下表结构,user_id的字段类型是字符串,而用户传入的是int,这里会有一个隐式转换的问题隐式类型转换会使索引失效,导致全表扫描。把输入改成字符串类型,查看执行计划“explain select id, user_id, name from test_2 where user_id=‘1’”,就会用到索引,如图4-31所示。

第5章 MySQL服务器全面优化

5.1 MySQL 5.7 InnoDB存储引擎增强特性

5.2 硬件层面优化

5.3 Linux操作系统层面优化

5.4 MySQL配置参数优化

5.5 MySQL设计规范

第6章 MySQL性能监控

6.1 监控图表的指导意义

6.2 Lepus数据库监控系统实战

6.2.1 Lepus数据库监控系统简介

6.2.2 Lepus数据库监控系统部署

6.2.3 监控MySQL服务器

6.2.4 Lepus慢查询分析平台

第7章 MySQL主从复制详解【*】

​ MySQL的主从复制功能是构建基于MySQL数据库的高可用、高性能的应用程序基础,既能用于分担主数据库的读负载,也为高可用HA等工作提供了更多的支持主从复制是指数据可以从一个MySQL数据库服务器主节点复制到另外一个或多个MySQL数据库服务器从节点主从复制可以用于数据实时备份、读写分离、高可用HA等企业场景中

7.1 主从复制的概念和用途

​ MySQL的主从复制概念,如图7-1所示。对于修改MySQL-A数据库的操作,在数据库中执行后都会写入本地的日志系统A中。假设实时地将变化了的日志系统中的数据库事件操作通过网络发给MySQL-B,MySQL-B收到后,写入本地日志系统B,然后一条条地将数据库事件在数据库中完成。所以,MySQL-A变化,MySQL-B也会变化,通过这个机制可保证MySQL-A数据库和MySQL-B数据库的数据同步。

​ 在上面的模型中,MySQL-A是主服务器,即master;MySQL-B是从服务器,即slave。日志系统A其实是MySQL日志类型中的二进制日志binlog,专门用来保存修改数据库表的所有动作。日志系统B并不是二进制日志,而是中继日志,即relay-log,因为它是从MySQL-A的二进制日志复制过来的,并不是自己的数据库变化产生的。这就是所谓的MySQL的主从复制。报表等读负载可以在从服务器的数据库上查询,同时当主服务器出现问题时,可以切换到从服务器。

下面看一下主从复制的场景用途(作用)。

  • (1)应用场景1:从服务器作为主服务器的实时数据备份

    主从服务器架构的设置可以大大加强MySQL数据库架构的健壮性。例如,做数据的热备,当主数据库服务器故障出问题后可切换到从服务器继续提供服务,此时从服务器的从数据库的数据和宕机时的主数据库的数据几乎是一致的,而且可以在从服务器进行备份,避免备份期间影响主服务器服务。

  • (2)应用场景2:主从服务器实时读写分离,从服务器实现负载均衡

    主从服务器架构可通过程序(PHP、Java等)或代理数据库中间件实现对用户(客户端)的请求读写分离,如图7-2所示,即让从服务器仅仅处理用户的select查询请求,降低用户查询响应时间及读写同时在主服务器上带来的访问压力。更新的数据(例如update、insert、delete语句)仍然交给主服务器处理,以确保主服务器和从服务器保持实时同步。

  • (3)应用场景3:把多个从服务器根据业务重要性进行拆分访问

    可以把几个不同的从服务器根据公司的业务进行拆分。例如,有为外部用户提供查询服务的从服务器,有内部DBA用来数据备份的从服务器,还有为公司内部人员提供访问的后台、脚本、日志分析及供开发人员查询使用的从服务器。这样的拆分除了减轻主服务器的压力外,还可以使数据库对外部用户浏览、内部用户业务处理及DBA人员的备份等互不影响。

7.2 主从复制的原理及过程描述

​ MySQL之间主从复制的基础是二进制日志文件(binary log file)。(过程如下:)一个MySQL数据库启用二进制日志后,作为master,数据库中的所有操作都会以“事件”的方式记录在二进制日志中。其他数据库作为slave通过一个I/O线程与master保持通信,并监控master二进制日志文件的变化,如果发现master二进制日志文件发生变化,就会把变化复制到自己的中继日志中,然后由一个SQL线程把相关的“事件”执行到自己的数据库中,以实现从数据库和主数据库的一致性,也就是实现了主从复制

​ 在主从复制的过程中,首先必须打开master主节点的binary log(binlog)功能,否则无法实现。因为整个复制过程实际上就是slave从节点从master主节点获取日志然后在自己身上完全顺序地执行日志中所记录的各种操作。

​ MySQL主从复制涉及3个线程,一个运行在主节点(log dump thread),其余两个(I/O thread、SQL thread)运行在从节点,如图7-3所示。

​ 主节点会创建一个binlog dump线程,用于发送binlog的内容。当从节点上执行start slave命令开启主从复制之后,从节点会创建一个I/O线程,用来连接主节点,并请求从指定binlog日志文件的指定位置之后的日志内容。主节点接收到来自从节点的I/O请求后,通过负责复制的binlog dump的I/O进程,根据从节点的I/O线程请求的信息分批读取binlog日志文件所指定位置之后的日志信息,返回给从节点。返回信息中除了日志所包含的信息之外,还包括本次返回信息的binlog文件名以及binlog的位置。

​ 从节点的I/O进程接收到内容后,将接收到的日志内容更新到本机的relay-log中,并将读取到的binlog文件名和位置保存到master-info文件中,以便在下一次读取的时候能够清楚地告诉master“我需要从某个binlog的哪个位置开始往后的日志内容,请发给我”。从节点上的SQL线程会实时检测到relay log中新增加了内容,将relay log的内容解析成具体的SQL语句操作,并在从节点上按解析SQL语句的位置顺序执行和应用这些SQL语句,最终保证主从数据的一致性。

7.3 主从复制的重点参数解析

​ 在MySQL数据库配置文件my.cnf中,启用主从复制过程中需要考虑的重要参数说明如下:

  • server-id:MySQL主从服务器上不能一样,这是同一组主从结构的唯一标识。

  • log-bin:开启二进制日志(搭建主从复制必须开启)

  • binlog_format:二进制日志的格式,有statement模式(基于SQL语句的复制)、row模式(基于行的复制)、还有mixed模式(混合复制),这里必须使用row模式。statement基于SQL语句的复制就是记录SQL语句在binlog中,缺点是在某些情况下会导致主从节点中的数据不一致(比如sleep()、now()等)。row基于行的复制是MySQL master将SQL语句分解为基于row更改的语句并记录在binlog中,也就是只记录哪条数据被修改了、修改成什么样,优点是不会出现某些特定情况下被正确复制的问题。mixed是以上两种模式的混合。

  • read_only:设置从库只读模式,可以限定普通用户进行数据修改的操作,但不会限定具有super权限的用户的数据修改操作,可以通过set global read_only=1设置从库只读状态。MySQL 5.7增加了一个super_read_only参数,一旦开启该参数,连超级管理员都没有权限进行写入操作。在MySQLslave库中设定了read_only=1以后,通过show slave status\G命令查看salve状态,发现salve仍然会读取master上的日志,并且在slave库中应用日志,不会影响slave同步复制的功能。

  • relay_log_recovery=1:当slave从库宕机后,若relay log损坏了,导致一部分中继日志没有处理,则自动放弃所有未执行的relay log,并重新从master上获取日志,这样就保证了relay log的完整性。默认情况下,该功能是关闭的,将relay_log_recovery的值设置为1时,可在slave从库上开启该功能。建议开启。

  • relay-log-info-repository=TABLE和master-info-repository=TABLE:在MySQL运行过程中宕机的话,从库启动后必须能够恢复到已经执行事务的位置,该信息传统上是存在文件中的,有可能存在不一致或者损坏的风险。从MySQL 5.7开始,可以用表来存储这些信息,并把这些表设置为InnoDB引擎,通过使用事务型存储引擎来恢复这个信息。

  • gtid_mode:是否开启gtid模式。若使用gtid模式,则设置gtid_mode=on。

  • enforce-gtid-consistency:enforce_gtid_consistency默认为off,可选[off|on],表示限定事务安全的SQL才允许被记录。例如,create table…select语句以及create temporary table语句不被允许执行。(create table … select会被拆分为两个事务,比如create table和insert事务,会导致相同的GTID分配给两个事务,从库会忽略。)

  • log_slave_updates:通常情况下,从服务器从主服务器接收到的更新不记入它的二进制日志。该选项的作用是将从master上获取数据变更的信息记录到slave的二进制日志文件中。对于级联复制A→B→C,也就是说,A为从服务器B的主服务器,B为从服务器C的主服务器。为了能工作,B必须既为主服务器又为从服务器。除了A和B启用二进制日志外,B服务器必须启用log-slave-updates选项。另外,MySQL 5.6的GTID复制模式也必须开启log_slave_updates参数,否则启动就会报错,因为需要在bin-log找到同步复制的信息。在MySQL 5.7里,官方做了调整,用一张gtid_executed系统表记录同步复制的信息,可以不用开启log_slave_updates参数,减少了从库的压力。

7.4 主从复制的部署架构

​ MySQL数据库支持单向、双向、链式级联、环状等不同业务场景的复制。在复制过程中,一台服务器充当主服务器(master),接收来自用户的内容更新,而一个或多个其他的服务器充当从服务器(slave),接收来自主服务器binlog文件的日志内容,解析出SQL重新应用到从服务器,使得主从服务器数据达到一致。

7.4.1 一主一从或一主多从

(1)一主一从或一主多从

​ 如图7-4所示,常见的master-slaves架构(大概90%的主从复制会使用这种架构)就是一台master复制数据到一台或多台slave,将master上的读压力分散到多台slave上,因为在很多系统中读压力往往会大于写压力。

7.4.2 多级主从(级联同步)

(2)多级主从(级联同步)

​ 如图7-5所示,如果设置了链式级联复制,那么从服务器(slave)本身除了充当从服务器外,也会同时充当其下面从服务器的主服务器。链式级复制类似A→B→C的复制形式,需要注意的是要复制的节点过多,会导致复制延迟。

7.4.3 双主

(3)双主

​ 如图7-6所示,可以搭建一个双主(master)环境,在这个双master环境里,两个MySQL Server互相将对方视为自己的master,自己作为slave。这样无论哪一方数据发生了更改都能同步到另一方,如果其中一个master停机维护,重启后也不会有任何数据问题。当然如果双master都同时提供写服务的话,也会有一定的数据冲突问题,虽然是双主,但是业务上同一时刻只允许对一个主进行写入。

7.4.4 多主一从(也称多源复制,MySQL 5.7之后开始支持)

(4)多主一从(也称多源复制,MySQL 5.7之后开始支持)

​ 如图7-7所示,多主一从使得从机从各主机同步接收业务信息(transactions),这样可以让一部服务器为多个主机服务器备份、合并数据表、联合数据。应用场景如数据汇总,可将多个主数据库同步汇总到一个从数据库中,方便数据统计分析,从库只用于查询。

7.5 异步复制

MySQL默认采用异步复制方式。所谓异步模式,指的是MySQL主服务器上I/O thread线程将二进制日志写入binlog文件之后就返回客户端结果,不会考虑二进制日志是否完整传输到从服务器以及是否完整存放到从服务器上的relay-log日志中。在这种模式下,主服务器宕机时,主服务器上已经提交的事务可能并没有传到从服务器上,如果强行将从服务器提升为主服务器,可能导致新主服务器上的数据不完整。

​ 下面看一下MySQL异步复制搭建过程,这里是基于binlog和position方式来搭建主从复制的。

7.5.1 搭建主从复制必要条件

  • (1)主库开启binlog功能(建议从库也开启binlog,并且开启log_slave_updates参数,方便后期扩展架构)。

  • (2)主库的server-id和从库的server-id保证不能重复,MySQL同步的数据中是包含server-id的,而server-id用于标识该语句最初是从哪个server写入,因此server-id一定要有,而且不能相同。如果server-id相同,那么同步就可能陷入死循环,会有问题。

  • (3)在MySQL中做主主同步时,多个主需要构成一个环状,但是同步的时候又要保证一条数据不会陷入死循环,要靠server-id来实现。

  • (4)为了保证后期不会出现数据不一致的情况,binlog格式要为row模式。

  • ( 5 )从库设置relay-log-info-repository=TABLE 、 master-info-repository=TABLE 和relay_log_recovery=1。其中,前两个选项的作用是确保在slave上和复制相关的元数据以表的形式存放到数据库中,表采用InnoDB引擎,受到InnoDB事务安全的保护;后一个选项的作用是开启relay-log自动修复机制,发生crash时,会自动判断哪些relay-log需要重新从master上抓取回来再次应用,以此避免部分数据丢失的可能性。

  • (6)主库要建立主从复制账户账号(授予replication slave权限)。

7.5.2 主从复制具体搭建过程

​ 假设搭建一主一从的部署架构,master(主服务器)node0的IP地址是10.10.75.100,slave(从服务器)node1的IP地址是10.10.75.101。

  • (1)修改数据库配参数文件,如图7-8所示,为主库开启binlog功能;为了便于后期架构扩展,也为从库开启binlog功能。

  • (2)在主服务器node0(10.10.75.100)上建立复制账户并授权,如图7-9所示。

  • (3)初始化数据,备份主库,在从库上恢复,让从库与主库在某一位置时达到同步。这里用mysqldump做数据的备份导出操作,命令是“mysqldump -uroot -p123@abc --single-transaction --master-data=2 --databases mldn >/tmp/mldn.dmp”,如图7-10所示。如果数据量大,使用Xtrabackup进行热备份也是可以的。

    ​ 备注:加参数–databases mldn表示备份mldn数据库;加参数–master-data=2让备份出来的文件中记录备份这一时刻的binlog文件position号;加参数–single-transaction是为了得到一个一致性备份,在导出数据之前开启一个事务,由数据库保证单次导出数据的一致性,此时针对InnoDB表的所有读写操作均不会被阻塞。

    ​ 查看mldn.dmp,得到当前binlog文件名和position号,如图7-11所示。

    ​ 复制到从库的服务器上,在从库上恢复从主库导出来的数据,如图7-12所示。如果从服务器上没有mldn数据库,就在从库上创建一个。

  • (4)在从库上,执行MySQL配置主从命令:

    change master to master_host='10.10.75.100',
    master_user='mysync',master_password='q123456',
    master_port=3306,
    master_log_file='mysql-bin.000001',master_log_pos=1627;
    

    ​ 在MySQL主从配置命令中,master_host指的是主库的IP地址;master_user、master_password、master_port指的是之前在主库上创建的复制账户、用户密码、数据库端口号;master_log_file和master_log_pos指的是从备份文件中获取的当前二进制文件名以及position号。

    ​ 如 果 出 现 报 错 信 息 “ERROR 1776 (HY000): Parameters MASTER_LOG_FILE, MASTER_LOG_POS, RELAY_LOG_FILE and RELAY_LOG_POS cannot be set when MASTER_AUTO_POSITION is active.”,就执行命令change master to master_auto_position=0,如图7-13所示。

  • (5)执行start slave命令,启动从服务器复制功能。在从服务器上查看主从复制状态命令是show slave status\G,如图7-14所示。

    ​ 其中,Slave_I/O及Slave_SQL进程必须正常运行,即Yes状态,否则都是错误的状态(只要其中一个是NO就属错误)。

     Slave_I/O_Running: Yes //此状态必须是YesSlave_SQL_Running: Yes //此状态必须是Yes......
    

    ​ Master_Log_File是当前主库的二进制文件名。

    ​ Read_Master_Log_Pos是正在读取主库当前二进制日志的位置。

    ​ Exec_Master_Log_Pos是执行到主库二进制日志中的位置。

    ​ 判断节点是否延迟,查看Seconds_Behind_Master,这里为0。

    ​ 完成以上操作过程,主从服务器配置完成。

  • (6)主从服务器测试验证是否可以正常同步数据,可以在主库mldn中建表,并插入数据,在从库上查看主库上新插入的数据。

  • (7)如果需要禁用主从复制,只要在从服务器上执行命令stop slave,关闭主从同步即可。执行命令reset slave all,可以清空从库的所有配置信息。

    至此,MySQL主从复制搭建成功。

​ 关于如何保证主从复制数据一致性的问题,需要注意的是在MySQL中一次事务提交后需要写undo、redo、binlog、数据文件等。在这个过程中,可能在某个步骤发生crash,导致主从数据不一致。为了避免这种情况,我们需要调整主从上面相关选项的配置,确保即便发生crash了,也不能发生主从复制的数据丢失。在master主库上修改配置参数innodb_flush_log_at_trx_commit=1和sync_binlog=1,确保数据高安全。在slave从库上修改配置参数master_info_repository=“TABLE” 和relay_log_info_repository="TABLE"和relay_log_recovery=1,确保在slave上和复制相关的元数据表也采用InnoDB引擎,受到InnoDB事务安全的保护,并开启relay-log自动修复机制,这样,在发生crash时根据relay_log_info中记录的已执行的binlog位置从master上重新抓取回来再次应用,以避免部分数据丢失的可能性。为了避免人为误操作在从库中修改数据,导致主从数据不一致,从节点上授权只读模式set global read_only=1(只读模式不会影响slave同步复制的功能,但此限制对于拥有的super权限用户无效,MySQL 5.7版本增加了新的参数,使用set global super_read_only=ON限制超管用户)。

​ 关于主从复制延迟大的问题,建议使用和主库规格一样好的硬件设备作为slave,存储采用PCIE-SSD才是王道,当然最好使用MySQL 5.7版本,因为MySQL 5.7版本中复制性能有极大的增强。

7.6 半同步复制

7.6.1 半同步复制概念和原理

​ 首先了解一下全同步复制的概念:master主库提交事务,直到事务在所有slave从库都已提交,才会返回客户端事务执行完毕信息优点是能够确保将数据都实时复制到所有的从库,缺点是完成一个事务可能造成延迟,会影响主库的更新效率,所以全同步复制的性能必然会受到影响

MySQL复制默认是异步复制,复制效率很高,缺点也很明显,master将事件写入binlog,提交事务,自身并不知道slave是否接收、是否处理,所以不能保证所有事务都被所有slave接收。

(这是半同步复制的目的:)为了保证在主库出现问题的时候至少有一个从库的数据是完整的,MySQL引入了半同步复制功能

(半同步概念:)半同步复制介于异步复制和全同步复制之间,主库在执行完客户端提交的事务后不是立刻返回给客户端,而是等待至少一个从库接收到并写到relay-log中才返回给客户端,主库不需要等待所有从库给主库反馈。(特点:)相对于异步复制,半同步复制提高了数据的安全性,同时也造成了一定程度的延迟,至少是一个TCP/IP往返的时间。所以,半同步复制最好在低延时的网络中使用。在等待超时的情况下,半同步复制也会转换为异步复制,以保障主库业务的正常更新

​ MySQL 5.5-5.6的半同步复制原理如图7-15所示,主库将事务写入binlog,并传递给从库,刷新到中继日志,同时主库存储引擎层提交事务,之后主库开始等待从库的确认反馈,只有收到从库的回复后,主库才将“Commit OK”的结果反馈给客户端。这里有潜在的隐患,若客户端事务在存储引擎层提交后,在得到从库反馈确认的过程中主库宕机了,则可能的情况有两种:

  • ① 事务还没发送到从库上,此时客户端会收到事务提交失败的信息,客户端会重新提交该事务到新的主库上,当宕机的主库重新启动后,以从库的身份重新加入到该主从结构中,会发现事务在从库中被提交了两次,一次是之前作为主库的时候,一次是被新主库同步过来的;
  • ② 事务已经发送到从库上,此时从库已经收到并应用了该事务,但是客户端仍然会收到事务提交失败的信息,重新提交该事务到新的主库上。

​ 针对上述潜在问题,MySQL 5.7引入了一种新的半同步方案,无数据丢失的半同步复制。针对图7-15所示,“Waiting Slave dump”被调整到“Storage Commit”之前,接收到从库的反馈回复后,再提交事务并返回“Commit OK”。当然,之前的半同步方案同样支持,MySQL 5.7引入了一个新的参数进行控制:rpl_semi_sync_master_wait_point。这个参数有两种取值:AFTER_SYNC,新的半同步方案,Waiting Slave dump在Storage Commit之前;AFTER_COMMIT,是老的半同步方案。

​ MySQL 5.7版本极大地提升了半同步复制的性能。MySQL 5.6版本的半同步复制,dump thread承担了两份不同且又十分频繁的任务,即传送binlog给slave,并且需要等待slave反馈信息,而且这两个任务是串行的,dump thread必须等待slave返回之后才会传送下一个events事务。dump thread已然成为整个半同步提高性能的瓶颈。在高并发业务场景下,这样的机制会影响数据库整体的TPS。MySQL 5.7版本的半同步复制中,独立出一个ack collector thread,专门用于接收slave的反馈信息。这样master上有两个线程独立工作,可以同时发送binlog到slave和接收slave的反馈。

7.6.2 半同步复制配置

​ 半同步复制搭建很简单,在MySQL已经配置好主从复制(异步复制)的基础上,安装半同步复制功能插件即可。

​ 在主库上安装半同步复制插件和开启半同步复制功能,并且将超时等待的时间调整一个比较大的值(默认10秒),防止由于网络抖动导致超时向异步复制切换,如图7-16所示。

​ 在从库上也安装半同步复制插件和开启半同步复制功能,如图7-17所示。

​ 为了MySQL重启后能自动启动半同步复制功能,可以把rpl_semi_sync_master_enabled=onA04-、rpl_semi_sync_slave_enabled=on这两个参数分别加载到主从服务器的my.cnf配置文件中。

​ 通过stop slave io_thread和start slave io_thread命令,重启从库的I/O线程,激活半同步复制。

​ 在主库上查看半同步复制是否正常运行,如图7-18所示,参数Rpl_semi_sync_master_clients代表的是已经有一个从库连接到了主库,而且是半同步模式。参数Rpl_semi_sync_master_status是ON(开启)状态,代表已经是半同步复制模式。

​ 在从库上查看半同步复制状态,如图7-19所示,Rpl_semi_sync_slave_status参数值为ON代表从库也开启了半同步复制模式。

7.7 GTID复制

7.7.1 GTID特性和复制原理介绍

​ GTID(Global Transaction ID,全局事务标识)是对于一个已提交事务的编号,并且是一个全局唯一的编号,并且一个事务对应一个GTID。GTID实际上是由UUID+TID组成的。其中,UUID是一个MySQL实例的唯一标识。TID代表了该实例上已经提交的事务数量,并且随着事务提交单调递增

​ GTID是用来代替传统复制的方法,与普通复制模式的最大不同就是不需要指定二进制文件名和位置。在传统的复制里面,首先从服务器上在一个特定的偏移量那里连接到一个给定的二进制日志文件binlog,然后主服务器从给定的连接点position开始发送所有的事件。当发生故障时,需要主从切换,找到binlog和position点,然后将主节点指向新的主节点,相对来说比较麻烦,也容易出错。加入全局事务ID来强化数据库的主备一致性,用于取代过去通过binlog文件偏移量定位复制位置的传统方式。借助GTID,在发生主备切换的情况下,MySQL的其他Slave可以自动在新主上找到正确的复制位置,大大简化了复杂复制拓扑下集群的维护,也减少了人为设置复制位置发生误操作的风险。

​ GTID用于在binlog中唯一标识一个事务。当事务提交时,MySQL Server在写binlog的时候会先写一个特殊的binlog Event,类型为GTID_Event,指定下一个事务的GTID,再写事务的binlog。主从同步时GTID_Event和事务的binlog都会传递到从库,从库在执行的时候也是用同样的GTID写binlog,这样主从同步以后,就可以通过GTID确定从库同步到的位置了。也就是说,无论是级联情况还是一主多从情况,都可以通过GTID自动找点同步,而无须像之前那样通过binglog文件名和position找点了。这就是GTID复制的优势,更简单地搭建主从复制,更简单地实现failover。

1.GTID实现的工作原理

​ 关于基于GTID实现的工作原理如下:

  • (1)主节点更新数据时,会在事务前产生GTID,一同记录到binlog日志中。

  • (2)从节点的I/O线程将变更的binlog写入到本地的relay-log中。

  • (3)SQL线程从relay-log中获取GTID,然后对比本地binlog是否有记录(MySQL从节点必须开启binary log)。如果有记录,就说明该GTID的事务已经执行,从节点会忽略。如果没有记录,从节点就会从relay-log中执行该GTID的事务,并记录到binlog。

  • (4)在解析过程中会判断是否有主键,如果没有就用二级索引。如果没有二级索引,就用全部扫描。

​ 支持启用GTID对运维人员来说应该是一件高兴的事。在配置主从复制的传统方式里,需要找到binlog和position点,然后通过“change master to”命令指向新的主库,不是很有经验的运维人员往往会找错,造成主从同步复制报错。在MySQL 5.6里,如果使用了GTID,启动一个新的复制从库或切换到一个新的主库,就不必依赖log文件或者position点。只需要知道master的IP、端口、账号密码即可,因为同步复制是自动的,MySQL通过内部机制GTID自动找点同步。

2.GTID的限制

  • (1)不支持非事务引擎(从库报错,stopslave; start slave;忽略)。

  • (2)不支持create table … select语句复制(主库直接报错)。原理是会生成两个SQL:一个是DDL创建表SQL,一个是insert into插入数据的SQL。由于DDL会导致自动提交,因此SQL至少需要两个GTID,但是在GTID模式下只能给这个SQL生成一个GTID。

  • (3)不允许在一个SQL同时更新一个事务引擎和非事务引擎的表。

  • (4)在一个复制组中,必须统一开启CTID或是关闭GTID。

  • (5)开启GTID需要重启(MySQL 5.7中不需要)。

  • (6)开启GTID后,就不再使用原来的传统复制方式。

  • (7)对于create temporary table和drop temporary table语句不支持。

  • (8)不支持sql_slave_skip_counter(由于在这个GTID中必须是连续的,正常情况下同一个服务器产生的GTID是不会存在空缺的。所以不能简单地skip掉一个事务,只能通过注入空事务的方法替换掉一个实际操作事务)。

7.7.2 GTID复制配置实战

暂定

7.8 多源复制

​ MySQL 5.7在复制方面有了很大的改进和提升,比如开始支持多源复制(multi-source)以及真正地支持并行(多线程)复制。如图7-29所示,多源复制是指一个slave从实例指向多个master主实例,相当于把多个MySQL实例的数据汇聚到一个实例上面

1.多源复制的使用场景

  • (1)数据分析部门会需要各个业务部门的部分数据做数据分析,这时可以使用多源复制把各个主数据库的数据复制到统一的数据库中。

  • (2)在从服务器进行数据的汇总,如果我们的主服务器进行了分库分表的操作,为了实现后期的一些数据统计功能,往往要把数据汇总在一起再进行统计。

  • (3)在从服务器对所有主服务器的数据进行备份,在MySQL 5.7之前每个主服务器都需要一台从服务器,这样很容易造成资源的浪费,同时也加大了DBA的维护成本,但MySQL 5.7引入的多源复制可以把多个主服务器的数据同步到一台从服务器进行备份。

2.多源复制的必要条件

​ 要开启多源复制功能,必须在从库上设置master-info-repository和relay-log-info-repository这两个参数。

​ 这两个参数是用来存储同步信息的,可以设置的值为FILE和TABLE,默认是FILE。比如master-info就保存在master.info文件中,relay-log-info保存在relay-log.inf文件中,服务器如果意外关闭,主从间的同步信息文件没有来得及更新,就会造成数据的丢失。为了数据更加安全,通常设置为TABLE。这些表都是InnoDB类型的,支持事务,相对于文件存储安全得多。在MySQL库下可以看到这两个表的信息分别是mysql.slave_master_info和mysql.slave_relay_log_info ,这两个参数也是可以动态调整的。

3.多源复制搭建过程

​ 环境如图7-30所示,这里使用两主一从的架构基于MySQL 5.7版本的GTID多源复制。node0和node1这两个主库服务器不能有相同的数据库名字,否则就会在从库出现数据覆盖的现象。node0→node2和node1→node2要拥有不同的复制账号,这3台服务器之间的数据库参数配置要保证开启GTID复制功能 (gtid_mode=on和enforce_gtid_consistency=on),binlog格式为row模式,server-id之间都不同,另外从库的参数需要配置master_info_repository=table和relay_log_info_repository=table,即主从复制的信息需要记录到表中。

  • (1)分别在node0和node1上创建复制账号。

    在node0的mysql数据库上创建复制账号:

    create user 'bak'@'10.10.75.%' identified by 'bak123456';
    grant replication slave on *.* to 'bak'@'10.10.75.%';
    flush privileges;
    

    在node1的mysql数据库上创建复制账号:

    create user 'repl'@'10.10.75.%' identified by 'repl123456';
    grant replication slave on *.* to 'repl'@'10.10.75.%';
    flush privileges;
    
  • (2)在node0和node1中使用mysqldump工具导出需要备份的tz和ts数据库,传递到从库node2机器上。

    node0上的操作命令:

    mysqldump -uroot -p123@abc --master-data=2 --single-transaction tz >/tmp/tz.tmp
    scp /tmp/tz.dmp 10.10.75.102:/tmp/
    

    node1上的操作命令:

    mysqldump -uroot -p123@abc --master-data=2 --single-transaction ts >/tmp/ts.tmp
    scp /tmp/tz.dmp 10.10.75.102:/tmp/
    
  • (3)在node2从库上进行恢复操作,命令如下:

    mysql -uroot -p123@abc tz </tmp/tz.dmp
    mysql -uroot -p123@abc ts </tmp/ts.dmp
    
  • (4)在从库上分别配置masterA ->slave和masterB ->slave的同步过程。每一个复制关系叫作一个复制通道channel,这点从执行change master命令的时候可以看出来。这里创建for channel ‘m1’、for channel 'm2’两个通道来管理从库通往主库的通道,有m1和m2两个通道。

    change master to master_host='10.10.75.100',master_user='bak', master_password='bak123456',master_
    change master to master_host='10.10.75.100',master_user='repl', master_password='repl123456',maste
    
  • (5)开启主从复制,通过start slave for channel 'm1’和start slave for channel 'm2’分别来开启。通过show slave status for channel ‘m1\G’、show slave status for channel 'm2\G’来分别查看复制源m1和m2的主从同步状态信息,也可以通过performance_schema.replication_connection_status表中的内容来监控主从复制状态。

  • (6)在node0上往tz库下的表插入数据,在node1上往ts库下的表插入数据,在从服务器node2上查看数据是否有进来。

至此,MySQL 5.7多源复制两主一从架构搭建成功。

7.9 主从复制故障处理

7.9.1 主从复制在生产环境中常见的故障

1.主从复制在生产环境中常见的故障

  • (1)主从故障之主键冲突,错误代码1062。

    • 原因:在从库上执行了写操作,比如插入记录,然后在主库上执行相同的SQL语句,主键冲突,主从复制状态就会报错。在从库上执行show slave status\G,会发现报错last_error:1062, SQL线程已经停止工作。
    • 解决方法:利用percona-toolkit工具中的pt-slave-restart命令在从库跳过错误(因为主从库都有相同的数据)。pt-slave-restart是percona-toolkit工具集中一个专用于处理复制错误的工具,若没有,则安装percona-toolkit工具。
  • (2)主库更新数据,从库找不到而报错,错误代码为1032。

    • 原因:在从库执行delete删除操作,再在主库执行更新操作,由于从库已经没有该数据,导致主从数据不一致了。
    • 解决方法:在从库执行show slave status命令,根据错误信息所知道的binlog文件和position号在主库上通过mysqlbinlog命令查找在主库执行的哪条SQL语句导致的主从报错。把从库上丢失的这条数据补上,然后执行跳过错误,主从复制功能就恢复正常了。如果从库缺失了很多条数据,就可以考虑重新搭建主从环境。
  • (3)在主库中设置binlog-do-db=xxx的库复制过滤规则,并且在statement binlog格式下执行跨库操作,导致从库没有复制成功。

    • 原因:主库参数文件有binlog-do-db设置,并且主库的binlog_format设置为statement,进行跨库操作的时候,数据不能复制到从库上,导致主从数据不一致。
    • 解决方法:主库的binlog格式要为row模式,另外在主库上尽量避免使用库复制过滤原则,可以在从库上使用replicate-do-db或者replicate-ignore-db参数。

7.9.2 主从复制的数据一致性检查

2.主从复制的数据一致性检查

​ 主库宕机,把从库提升为主库,主从库之间的数据一致性不能保证,我们就会利用perconna-toolkit工具集中的pt-table-checksum工具来检查主从数据的一致性,然后通过pt-table-sync工具来修复不一致的数据。

​ 为了演示,事先故意在从库mldn下的demo2表修改一条记录,如图7-31所示。

​ 下载percona-toolkit-3.0.1-1.el6.x86_64.rpm后,执行安装percona-toolkit工具的命令“yum -y install percona-toolkit-3.0.1-1.el6.x86_64.rpm”。

​ 工具pt-table-checksum在主库上执行校验检查,操作命令如下:

pt-table-checksum --nocheck-replication-filters --replicate=mldn.checksums --no-check-binlog-format --database=mldn -uroot -p123@abc -h10.10.75.100 -P3306

​ 输出结果分析,如图7-32所示。

  • TS:完成检查的时间。

  • ERRORS:检查时候发生错误和警告的数量。

  • DIFFS:0表示一致,1表示不一致。本例中值为1,代表出现了数据不一致的情况。

  • ROWS:表的行数。

  • CHUNKS:被划分到表中的块的数目。

  • SKIPPED:由于错误或警告过大而跳过块的数目。

  • TIME:执行的时间。

  • TABLE:被检查的表名。

​ 在主库执行完命令,会在主从服务器的mldn库下分别生成一张checksums表,把检查信息都写到了checksums表中。pt-table-checksum原理就是针对某张表中的所有字段进行hash函数运算,在主库和从库上分别经过运算后,把得到的值的记录进行比较来判断主从之间的数据一致性。

​ 如 果 运 行 pt-table-checksum 报 错 “install_driver(mysql) failed: Attempt to reload DBD/mysql.pm aborted.”,则需要卸载perl-DBD后重装,如图7-33所示。

​ 如果发现主从不一致,就使用pt-table-sync工具来修复。在主库用pt-table-sync打印出修复不一致数据的SQL语句,这里需要配合–print参数,命令格式如下:

pt-table-sync –print --sync-to-master h='从库IP',P=3306,u=root,p='密码' --databases=库名 --tables=表

​ 在主库上执行pt-table-sync命令,打印出需要执行修复的SQL语句,结果如图7-34所示。

​ 执行数据修复语句,将主库上的数据同步到从库,只同步mldn库demo2表。执行pt-table-sync命令,使用-execute参数来真正修复主从服务器mldn库不一致的数据。操作命令如下:

pt-table-sync --execute h=10.10.75.100,D=mldn,t=demo2,u=root,p=123@abc h=10.10.75.101,u=root,p=123

​ pt-table-sync命令的输出结果如图7-35所示。

​ 最后校验主从数据是否一致,如图7-36所示,结果显示diff记录为ie0,没有差异,表示mldn库下的demo2表主从数据一致。

7.10 主从延迟解决方案和并行复制

MySQL的主从复制都是单线程的操作,主库对所有DDL和DML产生的日志都写进binlog,由于binlog是顺序写,因此效率很高。slave的SQL thread线程将主库的DDL和DML操作事件在slave中重放,由于在主库上事务的提交是并发模式的,而从库只有一个SQL thread工作,当主库的并发较高时,产生的DML数量超过slave的SQL thread所能处理的速度,而且通常从库的硬件配置没有主库的好,那么主从延时必然会产生。

7.10.1 主从延时排查方法

1.主从延时排查方法

​ 简单粗略的方法是通过监控show slave status命令输出的Seconds_Behind_Master参数的值来判断,如图7-37所示,该值为0,表示主从复制良好(如果是正值,表示主从已经出现延时,数字越大,表示从库延迟越严重)。

​ 传统的通过show slave status\G命令中的Seconds_Behind_Master值来判断主从延迟有时并不靠谱。另外,建议使用percona-toolkit工具集的一个工具pt-heartbeat来监测主从延迟的情况。pt-heartbeat的工作原理是在主库上创建一张heartbeat表,按照一定的时间频率更新该表的字段(把时间更新进去),然后连接到从库上检查复制的时间记录,和从库的当前系统时间进行比较,得出时间的差异。

​ 在主库(10.10.75.100)上开启守护进程来更新heartbeat表,执行命令如“pt-heartbeat --update -h 10.10.75.100 -u root -p123@abc-D mldn --create-table–daemonize”。其中,–update表示每秒更新一次heartbeat表的记录;-D是–database的缩写,指的是heartbeat表所在的database。在第一次运行时,需带上–create-table参数创建heartbeat表并插入第一条记录。也可加上–daemonize参数,让该脚本以后台进程运行。

​ 在从库(10.10.75.101)上执行命令,如图7-38所示,可用–monitor参数或者–check参数。其中,–monitor参数是持续监测并输出结果,–check参数是只监测一次就退出,–master-server-id指定主库的server_id,0表示从没有延迟;[0.00s,0.00s,0.00s]表示1m、5m、15m的平均值。

​ 通过pt-heartbeart工具可以很好地检测主从延迟的时间,但需要搞清楚该工具的原理。默认的Seconds_Behind_Master值是通过将服务器当前的时间戳与二进制日志中的事件时间戳相对比得到的,所以只有在执行事件时才能报告延时。

7.10.2 延迟的优化解决方法

2.延迟的优化解决方法

  • ( 1 ) 建 议 从 库 的 硬 件 配 置 要 和 主 库 一 样 , 强 烈 建 议 使 用 SSD 硬 盘 , 并 且 修 改 配 置 参 数innodb_flush_method为O_DIRECT,提升写入性能。

  • (2)适当增大从库参数innodb_buffer_pool_size的值,减少I/O压力。

  • (3)主库对数据安全性较高,比如sync_binlog=1、innodb_flush_log_at_trx_commit=1之类的设置 , 而 slave 从 库 则 不 需 要 这 么 高 的 数 据 安 全 , 完 全 可 以 将 sync_binlog 设 置 为 0 或 者 500 , 且innodb_flushlog_at_trx_commit也可以设置为2来提高SQL的执行效率,以减少磁盘I/O压力。

  • (4)表设计时就要定义有主键,没有主键的话,数据量巨大的时候更新会导致大量主从延时。

  • (5)拆分大事务语句到若干小事务中,这样能够进行及时提交,减小主从复制延时。

  • (6)修改参数master_info_repository、relay_log_info_repository为TABLE,减少直接I/O导致的磁盘压力。

  • (7)升级到MySQL 5.7版本,才可称为真正的并行复制,其中最为主要的原因就是slave服务器的回放与主机是一致的。也就是说,主服务器上是怎么并行执行的,从库上就怎样进行并行回放。

7.10.3 MySQL 5.7并行复制Multi-Threaded Slave原理(简称MTS)

3.MySQL 5.7并行复制Multi-Threaded Slave原理(简称MTS)

​ 从MySQL 5.6.3版本开始就支持所谓的并行复制了,但是其并行只是基于schema的,也就是基于库的。其核心思想是:不同schema下的表并发提交时的数据不会相互影响,即slave节点可以用对relay-log中不同的schema各分配一个类似SQL功能的线程来重放relay-log中主库已经提交的事务,保持数据与主库一致。如果用户的MySQL数据库实例中存在多个schema,对于从机复制的速度的确可以有比较大的帮助。在一般的MySQL使用中,一库多表比较常见,如果用户实例仅有一个库,就无法实现并行回放,甚至性能会比原来的单线程更差。单库多表是比多库多表更为常见的一种情形。所以MySQL 5.6的并行复制对真正用户来说不太适合生产使用。

​ 下面简单地聊聊MySQL 5.7中的并行复制究竟是如何实现的。

​ MySQL 5.7的并行复制基于一个前提,即所有已经处于prepare阶段的事务都是可以并行提交的。这些当然也可以在从库中并行提交,因为处理这个阶段的事务都是没有冲突的,该获取的资源都已经获取了。反过来说,如果有冲突,则后来的会等已经获取资源的事务完成之后才能继续,故而不会进入prepare阶段。MySQL 5.7并行复制的思想一言以蔽之:通过对事务进行分组,如果事务能同时提交成功,那么它们就不会共享任何锁,这意味着它们没有冲突(否则不可能提交),一个组提交(group commit)的事务都是可以并行回放的,因为这些事务都已进入事务的prepare阶段,可以在slave上并行执行。所以通过在主机上的二进制日志中添加组提交信息,这些slave可以并行、安全地运行事务。

​ 如何知道事务是否在同一组中又是一个问题。MySQL 5.7二进制日志较原来的二进制日志内容多了last_committed和sequence_number。last_committed表示事务提交的时候上次事务提交的编号,如果事务具有相同的last_committed,就表示这些事务都在一组内,可以进行并行的回放。sequence_number是顺序增长的,每个事务对应一个序列号。另外,每一个组的last_committed值都是上一个组中事务的sequence_number最大值,也是本组中事务sequence_number最小值减1。

7.10.4 MySQL 5.7并行复制配置

4.MySQL 5.7并行复制配置

​ 在 MySQL 5.7 中 , 引 入 了 基 于 组 提 交 的 并 行 复 制 ( Multi-threaded Slaves ) , 设 置 参 数slave_parallel_workers>0并且slave_parallel_type=’LOGICAL_CLOCK’启用基于并行复制。

​ 参数变量slave_parallel_type可以有两个值:DATABASE默认值,基于库的并行复制方式;LOGICAL_CLOCK , 基于组提交的并行复制方式。开启并行复制MTS功能即可支持一个schema下slave_parallel_workers(并行的SQL线程数量)个worker线程并发执行relay-log中主库提交的事务。另外,务必确保将参数master_info_repository设置为TABLE,这样性能可以有50%~80%的提升。这是因为并行复制开启后对于master.info这个文件的更新将会大幅提升,资源的竞争也会变大。

​ 如图7-39所示,显示了开启并行复制MTS后slave服务器的QPS。测试的工具是sysbench的单表全update测试,测试结果显示在16个线程下的性能最好,从机的QPS可以达到25000以上,进一步增加并行执行的线程至32并没有带来更高的提升。原单线程回放的QPS仅在4000左右,由此可见MySQL 5.7 MTS带来的性能提升。

​ 从测试结果来看,MySQL 5.7的多线程复制在一定的TPS范围以内能够避免备库的大延迟, MySQL 5.7推出的Enhanced Multi-Threaded Slave在一定程度上解决了困扰MySQL长达数十年的复制延迟问题。如果MySQL 5.7要使用MTS功能,就必须使用最新版本,最少升级到5.7.19版本,修复了很多Bug。

第8章 PXC高可用解决方案

8.1 PXC概述

8.2 PXC的实现原理

8.3 PXC集群的优缺点

8.4 PXC中的重要概念

8.5 PXC集群部署实战

8.6 PXC集群状态监控

8.7 PXC集群的适用场景和维护总结

第9章 基于MHA实现的MySQL自动故障转移集群

9.1 MHA简介

9.2 MHA原理

9.3 MHA的优缺点

9.4 MHA工具包的功能

9.5 MHA集群部署实战

第10章 MySQL Group Replication

10.1 MGR概述

10.2 MGR基本原理

10.3 MGR服务模式

10.3.1 单主模式

10.3.2 多主模式

10.4 MGR的注意事项

10.5 MGR部署实战

10.6 MGR的监控

10.7 MGR的主节点故障无感知切换

第11章 Keepalived+双主复制的高可用架构

11.1 Keepalived+双主架构介绍

11.2 Keepalived介绍

11.3 双主+Keepalived集群搭建

第12章 数据库分库分表与中间件介绍【*】

​ 大型网站用户数和数据库规模急剧上升,关系型数据库常见的性能瓶颈主要体现在两点

  • 一是大量的并发读写操作,导致单库出现负载压力过大;
  • 二是单表存储数据量过大,导致查询效率低下。

这时常见的做法便是对数据库实施分库分表,即Sharding改造来应对海量数据和高并发对数据库的冲击,与此同时,支持分库分表并且对业务开发透明的数据库中间件也大行其道。

12.1 关系数据库的架构演变

12.1.1 数据库读写分离(解决访问压力)

​ 随着网站的业务不断扩展,数据不断增加,用户越来越多,数据库的压力越来越大。在大部分互联网业务场景中,读操作的比例远远大于写操作,而读取数据通常耗时比较长,占用数据库服务器的CPU较多,从而影响用户体验。这时,数据库的读压力会首先成为数据库的瓶颈,而此时SQL的查询优化已很难达到要求了在数据库层面,我们首先采用的是数据库读写分离技术,消除读写锁冲突,来提升业务系统的读性能

​ 数据库读写分离其实就是将数据库分为主从库,如图12-1所示,一个主库用于写数据,多个从库用来完成读数据的操作,主从库之间通过某种机制进行数据的同步(这里就涉及到主从复制,所以主从复制、读写分离、负载均衡是在一起的),是一种常见的数据库架构。让主库负责数据更新和实时数据查询,从库负责非实时数据查询,同时多个从库之间使用负载均衡,减轻每个从库的查询压力。

如果主库的TPS(Transaction Per Second,每秒事务处理量)较高,那么主库与从库之间的数据同步是会存在一定延迟的,因此在写入主库之前最好将同一份数据落到缓存,以避免高并发场景下从从库中获取不到指定数据的情况发生

12.1.2 数据库垂直分库(解决数据库量大)

​ 随着访问压力的增加,读写操作不断增加,数据库的压力越来越大,所以增加从服务器,做数据库读写分离。可是问题又来了,数据量急剧快速增长,数据库会成为整个系统的瓶颈。这时可以考虑按照业务把不同的数据放到不同的库中(把不同的,没有关系的表放到不同的数据库中)

​ 其实在一个大型而且臃肿的数据库中,表和表之间的数据很多是没有关系的,或者根本不需要(join)操作,理论上就应该把它们分别放到不同的数据库。例如,用户收藏夹的数据和博客的数据就可以放到两个独立的数据库DB,这两个独立的数据库可以在同一个服务器上或者在不同的服务器上,这种方法就叫垂直切分

垂直分库与业务架构设计有密切的联系。比如从业务领域对系统进行架构优化,分成多个子业务系统,各个子业务系统耦合度较低,子业务系统间以接口方式进行数据通信和数据交换

​ 一般的电商平台系统都会包含用户、商品、订单等几大模块,但是随着业务的提升,将所有业务都放在一个库中会变得越来越难以维护,因此建议将不同业务放在不同的库中,如图12-2所示。

垂直切分后业务清晰,不同业务放在不同的库中,将原来所有压力由同一个库分散到不同的库中,提升了系统的吞吐量

12.1.3 数据库水平分库与水平分表(解决单表数据量大)

​ 垂直分库还是无法解决单表数据量过大的问题,由于单一业务的数据信息仍然落盘在单表中,如果单表数据量太大,就会极大地影响SQL执行的性能。水平分表是互联网场景下关系数据库中应对高并发、单表数据量过大的解决方案

分表就是将原本在单库中的单个业务表拆分为几个“逻辑相关” 的业务子表,不同的业务子表各自负责存储不同区域的数据,对外形成一个整体,让每个子表的数据量控制在一定范围内,保证SQL的性能,这就是常说的Sharding分片操作。

​ 如图12-3所示,以user_id字段为依据,按照一定策略(hash、range等),将一个表中的数据拆分到多个表中。每个表的结构都一样,每个表的数据都不一样,没有交集,所有表的并集是全量数据。原来单表的数据量太多,影响了SQL效率,加重了CPU负担,以至于成为瓶颈。现在单表的数据量少了,单次SQL执行效率高,自然减轻了CPU的负担。

​ 水平分表后的业务子表可以包含在单库中,也可以将水平分表后的这些业务子表分散到n个业务子库中(这难道就是分库分表吗?)。例如,将userID散列进行划分后存储到多个结构相同的表和不同的库上。举个例子,假设在userDB中的用户数据表中每一个表的数据量都很大,就可以把userDB切分为结构相同的多个userDB,如part0DB、part1DB、part2DB等,再将userDB 上 的 用 户 数 据 表 userTable 切 分 为 很 多 userTable , 如userTable0、userTable1、userTable3等,然后将这些表按照一定的规则存储到多个userDB上(大概描述:将原本的一张表划分为结构相同的n张表,这n张表分别存放一部分原本表中的数据,同时这n张表也可以放在不同的库中)。

(应用场景:)水平分表主要用于业务架构无法继续细分、数据库中单张表数据量太大、查询性能下降的场景。通过水平分表把原有逻辑数据库切分成多个物理数据库节点,表数据记录分布存储在各个节点上,即解决单库容量问题,同时提高并发查询性能。

(如何选择分库与分表?) 应该使用哪一种方式来实施数据库分库分表,需要从数据库的瓶颈所在和项目的业务角度进行综合考虑。

  • 如果数据库是因为表太多而造成海量数据,并且项目的各项业务逻辑划分清晰、耦合度较低,那么容易实施的垂直切分必是首选。
  • 如果数据库中的表并不多,但单表的数据量很大且数据热度很高,这种情况之下就应该选择水平切分, 水平切分比垂直切分要稍微复杂,它将原本逻辑上属于一体的数据进行了物理分割,除了在分割时要对分割的粒度做好评估外,还要考虑数据要如何均匀分散。

在现实项目中,往往是这两种情况兼而有之,综合使用了垂直与水平切分,我们首先对数据库进行垂直切分,然后针对一部分表(通常是用户数据表)进行水平切分。

12.2 分库分表带来的影响

​ 任何事情都有两面性,分库分表也不例外。采用分库分表也会引入新的问题。

12.2.1.分布式事务问题

​ 做了垂直分库或者水平分库以后必然会涉及跨库执行SQL的问题,从而引发互联网界的老大难问题“分布式事务”

​ 那么要如何解决这个问题呢?

  • 使用分布式事务中间件。

  • MySQL为我们提供了分布式事务解决方案。(这里需要重点去学习MySQL的分布式事务)

  • 尽量避免跨库操作(比如将用户和商品放在同一个库中)。

​ 分布式事务能最大限度地保证数据库操作的原子性,但在提交事务时需要协调多个节点,推后了提交事务的时间点,延长了事务的执行时间。对于那些性能要求很高但对一致性要求不高的系统,往往不苛求系统的实时一致性,只要在允许的时间段内达到最终一致性即可,可采用事务补偿的方式。与事务在执行中发生错误后立即回滚的 方式不同,事务补偿是一种事后检查补救的措施,要结合业务系统来考虑,比如对数据进行对账检查、基于日志进行对比等。

12.2.2.跨库join的问题

​ 分库分表后,表之间的关联操作将受到限制,无法join位于不同分库的表,也无法join分表粒度不同的表,结果导致原本一次查询能够完成的业务可能需要多次查询才能完成。

​ 那么要如何解决这个问题呢?

  • 全局表:基础数据,所有库都备份一份。

  • 字段冗余:把需要join的字段冗余在各个表中,这样有些字段就不用join去查询了。

  • 数据组装:在系统层面,分两次查询,在第一次查询的结果集中找出关联数据id,然后根据id发起第二次请求得到关联数据,最后将获得到的数据进行字段拼装。

  • ER分片:在关系型数据库中,如果可以先确定表之间的关联关系,并将那些存在关联关系的表记录存放在同一个分片上,就能较好地避免跨分片join问题。

12.2.3.结果集合并、排序的问题

​ 因为我们是将数据分散存储到不同的库、表里的,所以当我们查询指定数据列表时,数据来源于不同的子库或者子表,就必然会引发结果集合并、排序的问题。当排序字段就是分片字段时,通过分片规则就比较容易定位到指定的分片;当排序字段非分片字段时就变得比较复杂了,需要先在不同的分片节点中将数据进行排序并返回,然后将不同分片返回的结果集进行汇总和再次排序,最终返回给用户。

12.2.4.数据迁移、扩容问题

​ 当业务高速发展、面临性能和存储的瓶颈时才会考虑分片设计,不可避免地需要考虑历史数据迁移的问题。一般做法是先读出历史数据,然后按指定的分片规则将数据写入到各个分片节点中。此外,还需要根据当前的数据量、QPS以及业务发展的速度进行容量规划,推算出大概需要多少分片(一般建议单个分片上的单表数据量不超过1000W)。

​ 如果采用的是数值范围分片,只需要添加节点就可以进行扩容了,不需要对分片数据迁移。如果采用的是数值取模分片,考虑后期的扩容问题就相对比较麻烦。

上面列出了分库分表的常见问题,总地来说:

  • (1)能不切分尽量不要切分,并不是所有表都需要进行切分,主要还是看数据的增长速度,切分后会在某种程度上提升业务的复杂度,当数据量达到单表的瓶颈时再考虑分库分表。

  • (2)一定要切分时,一定要选择合适的切分规则,提前规划好**(怎样的规则?怎么来确定规则?)**。

  • (3)一定要切分时,尽量通过全局表、字段冗余等手段来降低跨库join的可能,业务读取尽量少使用多表join。

  • (4)尽可能比较均匀地分布数据到各个节点上。

12.3 常见的分库分表中间件介绍

​ 前面讲过对数据进行分片处理之后,从原有的一个库切分为多个分片数据库,所有的分片数据库集群构成了整个完整的数据库存储。在数据被分到多个分片数据库后,应用如果需要读取数据,就要需要处理多个数据源的数据。原本该是专注于业务的应用,却要花大量的工作来处理分片后的问题。一旦数据库实施分库分表后,应用开发人员就要考虑两个问题

  • (1)首先,必须明确定义SQL语句中的分片字段Shard Key(路由条件),因为路由维度直接决定了数据的落盘位置;
  • (2)其次,应该如何根据所定义的Shard Key进行数据路由,这需要定义一套特定的路由规则。

这两个问题解决后,就能让一条SQL语句的读/写操作准确定位到具体的业务表中。

​ 需要用一种解决方案来解决分片后的问题,而让应用只集中于业务处理。从技术角度来看,解决方案大致可以分为两大类,即数据库Sharding中间件原生分布式数据库

  • (1)原生分布式数据库的架构设计、底层存储和查询处理均面向分布式数据管理需求,数据库集群作为一个整体对外提供服务,用户无须关注集群内部的实现细节,如腾讯自研TDSQL数据库。对于原生分布式数据库系统来说,系统支持数据的自动分片以及分片副本在集群节点间的自动迁移和复制,实现负载均衡,在服务器利用率和管理复杂性上的优势明显。
  • (2)互联网行业最初所使用的分布式数据库方案多数是基于中间件的,在解决服务压力问题上也取得了较好的效果。Sharding中间件是架构在多个传统单点数据库系统上的中间层解决方案,通过将数据分拆到不同的数据库节点上,基于Proxy架构,利用中间件完成数据的路由工作。数据库中间件就是介于数据库与应用之间进行数据处理与交互的中间服务。

​ 目前市面上常见的Sharding中间件的产品有以下几种。

  • Amoeba(变形虫):amoeba是阿里在2008年推出的,当时正好是在阿里巴巴去IOE的浪潮中。这个软件致力于MySQL的分布式数据库前端代理层,主要在应用层访问MySQL的时候充当SQL路由功能,专注于分布式数据库代理层(Database Proxy)开发,具有负载均衡、读写分离功能。在2012年随着阿里业务量增长,Amoeba也越来越不适应当时不断增长的需求,同年在Amoeba的基础上阿里开源了替代Amoeba的产品Cobar。
  • Cobar:阿里巴巴于2012年6月19日正式对外开源的数据库中间件。前身是早已经开源的Amoeba,在其作者陈思儒离职去盛大之后,阿里巴巴内部考虑到Amoeba的稳定性、性能、功能支持和其他因素,重新设立了一个项目组,并且更换其名称为Cobar。Cobar是由阿里巴巴开源的MySQL分布式处理中间件,可以在分布式的环境下看上去像传统数据库一样提供海量数据服务。Cobar自诞生之日起就受到广大程序员的追捧,但是自2013年后几乎没有后续更新。
  • Mycat:是基于阿里巴巴开源的Cobar产品而研发的,Cobar的稳定性、可靠性、优秀的架构和性能,以及众多成熟的使用案例使得Mycat一开始就拥有一个很好的起点。
  • 社区爱好者在Cobar基础上进行二次开发,解决了Cobar当时存在的一些问题,并且加入了许多新的功能。目前Mycat社区活跃度很高,也有很多公司在使用Mycat,总体来说支持度比较高。业界优秀的开源项目和创新思路被广泛融入到MyCat的基因中,使得MyCat在很多方面都领先于目前其他一些同类的开源项目,甚至超越某些商业产品。
  • OneProxy:是由原支付宝首席架构师楼方鑫开发的,目前由楼方鑫创立的杭州平民软件公司提供技术支持。它是基于MySQL官方的proxy思想利用C语言进行开发的,是一款商业收费的中间件,专注在性能和稳定性上。目前已有多家公司在生产环境中使用,其中包括支付、电商等行业。
  • Atlas:是由Qihoo 360公司Web平台部基础架构团队开发维护的一个基于MySQL协议的数据中间层项目,使得应用程序员无须再关心读写分离、分表等与MySQL相关的细节。目前该项目在360公司内部得到了广泛应用,很多MySQL业务已经接入了Atlas平台,可以专注于编写业务逻辑,同时使得DBA的运维工作对前端应用透明。

第13章 Mycat中间件详解(核心:数据库分片)

​ 由于真正的数据库需要存储引擎,而Mycat并没有存储引擎,因此它并不是完全意义上的分布式数据库系统,可以更贴切地说成是数据库的中间件,就是介于数据库与应用之间进行数据处理与交互的中间服务。传统的访问数据库是直接连接数据库,创建数据库实例,根据需求对数据库中的数据进行增删查改。但是,当我们使用Mycat后,其实直接连接的是Mycat,通过Mycat对真正的数据库进行操作,在Mycat上我们可以做一些分表分库等操作,达到我们对数据库的可扩展要求。

13.1 Mycat简介

Mycat的前身是阿里巴巴的Cobar,核心功能和优势是数据库分片

​ Mycat的目的是打造真正的分布式数据库中间件,如图13-1所示。用户可以把Mycat看作是一个数据库代理,用MySQL客户端工具和命令行访问,而其后端可以用MySQL原生协议与多个MySQL服务器通信,也可以用JDBC协议与大多数主流数据库服务器通信。可以像使用MySQL一样使用Mycat,对于开发人员来说根本感觉不到Mycat的存在。

Mycat的核心功能是分表分库即将一个大表水平分割为N个小表,存储在后端多个数据库(主机)里,以达到分散单台设备负载的效果。应用程序就像连接普通的MySQL数据库一样地去连接它,SQL查询、操作等一模一样;而Mycat把数据库复杂的架构以及背后复杂的分表分库的逻辑全部透明化了。Mycat中间件连接多个MySQL数据库,多个数据库之间还可以做主从同步,这一切的一切对前端应用是完全透明的,不用调整前台逻辑,只要连接到Mycat即可。这样一来,对前端业务系统来说,业务代码无须过多调整,可以大幅降低开发难度,提升开发速度。

Mycat可实现数据库的读写分离,在后端的主从复制数据库集群中,通过Mycat配置将前端的写操作路由到主数据库中,将读操作路由到从数据库上。Mycat可以实现读写分离下的读操作负载均衡,将大量的读操作均衡到不同的从库上,主要出现在一主多从的情形下

​ Mycat可实现数据库的高可用,在数据库主节点可用的情况下,配置一台可写从节点,这两个节点都配置在Mycat中,当主节点宕机时,Mycat会自动将写操作路由到备用节点上,轻松实现热备份。

13.2 Mycat核心概念

​ Mycat的核心概念如图13-2所示。

  • (1)逻辑库(schema)

    Mycat作为一个中间件,实现MySQL协议,对前端应用连接来说就是一个数据库,无须让开发人员知道中间件存在,所以数据库中间件可以被当作一个或多个数据库集群构成的逻辑库。

  • (2)逻辑表(table)

    有逻辑库,就会有逻辑表。在分布式数据库中,对应用来说,读写数据的表就是逻辑表。逻辑表可以是数据切分后分布在一个或多个分片库中,也可以不做数据切分,不分片,只由一个表构成。

  • (3)分片表

    分片表是指那些原有的拥有很多数据、需要切分到多个数据库的表。每个分片都有一部分数据,所有分片构成了完整的数据。

  • (4)非分片表

    一个数据库中并不是所有的表都很大,某些表是可以不进行切分的。非分片表是相对分片表来说的,就是那些不需要进行数据切分的表。

  • (5)ER表

    关系型数据库是基于实体关系模型(Entity-Relationship Model)之上的,描述了真实世界中的事物与关系。Mycat中的ER表就来源于此。根据这一思路,提出了基于E-R关系的数据分片策略,子表的记录与所关联的父表记录存放在同一个数据分片上,即子表依赖于父表,通过表分组(Table Group)保证数据join关联查询不会跨库操作。表分组(Table Group)是解决跨分片数据join关联查询的一种很好的解决方法,也是数据切分规划很重要的一条原则。

  • (6)全局表

    在业务系统中,往往存在大量类似字典表的表,基本上很少变动。字典表的特性是:变动不频繁;数据量总体变化不大;数据规模不大,很少有超过数十万条记录的。当业务表因为数据量规模大而进行分片以后,业务表与这些附属的字典表之间的关联就成了比较棘手的问题,所以Mycat中通过数据冗余来解决这类表的join关联查询,即所有的分片都有一份数据的备份,所有将字典表或者符合字典表特性的一些表定义为全局表。数据冗余是解决跨分片数据join关联查询的一种很好的解决方法,也是数据切分规划的另外一条重要原则

  • (7)分片节点(dataNode)

    数据切分后,一个大表被分到不同的分片数据库上面,每个表分片所在的数据库就是分片节点。

  • (8)节点主机(dataHost)

    数据切分后,每个分片节点不一定都会独占一台机器,同一机器上面可以有多个分片数据库,这样一个或多个分片节点所在的机器就是节点主机。为了规避单节点主机并发数限制,尽量将读写压力高的分片节点均衡地放在不同的节点主机上。

  • (9)分片规则(rule)

    前面讲了数据切分,一个大表会被分成若干个分片表。按照某种业务规则把数据分到某个分片的规则就是分片规则。数据切分时选择合适的分片规则非常重要,将极大地避免后续数据处理的难度。

  • (10)全局序列号(sequence)

    数据切分后,原有的关系数据库中的主键约束在分布式条件下将无法使用,因此需要引入外部机制保证数据唯一性标识,这种保证全局性的数据唯一标识的机制就是全局序列号。

13.3 Mycat安装部署

​ 假设在node2(IP地址10.10.75.102)的机器上安装Mycat,node0节点(IP地址10.10.75.100)和node1节点(10.10.75.101)是MySQL数据库服务器。因为Mycat是用Java开发的,所以需要安装Java,官方建议jdk1.7及以上版本,上传并解压jdk-7u79-linux-x64.tar.gz。另外,还需要解压MySQL客户端。

​ 关闭Linux防火墙,配置好/etc/hosts文件,如图13-3所示。

​ 安装Mycat,要创建用户及组。创建一个新的group组,命令是“groupadd mycat”;创建一个新的用户mycat,并加入group组,命令是“useradd -g mycat mycat”;给新用户设置密码,命令是“passwdmycat”。把Mycat安装文件Mycat-server-1.5.1-RELEASE-linux.tar.gz解压到目录/usr/local下,并对当前目录下的所有目录以及子目录进行相同的拥有者变更,操作命令是“chown -R mycat.mycat/usr/local/mycat”。

​ 编辑/etc/profile文件,添加环境变量,如图13-4所示。保存文件后,需要执行命令source/etc/profile,使得刚修改的环境变量生效。

​ 修 改 /usr/local/mycat/conf/wrapper.conf 文 件 , 设 置 wrapper.java.command 的 Java 路 径 , 即wrapper.java.command =%JAVA_HOME%/bin/java,如图13-5所示。这一步非常重要,如果JVM参数配置错误,Mycat启动会报错误信息“Unable to start JVM: No such file or directory”。

​ 配置Mycat用本地XML方式,最重要的配置文件有schema.xml、server.xml、rule.xml,可以先将配置文件改名保存。

schema.xml作为Mycat中重要的配置文件之一,管理着Mycat的逻辑库、表、分片规则、dataNode等。弄懂这些配置是正确使用Mycat的前提。其中,schema标签用于定义Mycat实例中的逻辑库;table标签定义了Mycat中的逻辑表;dataNode标签定义了Mycat中的数据节点,也就是通常所说的数据分片;dataHost标签在Mycat逻辑库中也是作为最底层的标签存在的,直接定义分片所属的数据库实例;writeHost标签和readHost标签指定MySQL后端数据库。schema.xml示例如下:

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://org.opencloudb/"><!-- 定义一个Mycat的模式,此处定义了一个逻辑数据库名称TestDB --><!-- “checkSQLschema”:描述的是当前的连接是否需要检测数据库的模式 --><!-- “sqlMaxLimit”:表示返回的最大的数据量的行数 --><!-- “dataNode="dn1"”:该操作使用的数据节点是dn1的逻辑名称 --><schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"/><!-- 定义各数据的操作节点 --><dataNode name="dn1" dataHost="dthost1" database="mldn" /><!-- 包括了各种逻辑项的配置 --><dataHost name="dthost1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql"<!-- 配置真实MySQL与Mycat的心跳 --><heartbeat>select user()</heartbeat><!-- 配置真实的MySQL的连接路径 --><writeHost host="node0" url="10.10.75.100:3306" user="root" password="123@abc"></write></dataHost>
</mycat:schema>

​ server.xml几乎保存了所有Mycat需要的系统配置信息,常用的是配置用户名、密码及权限,示例如下:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mycat:server SYSTEM "server.dtd">
<mycat:server xmlns:mycat="http://org.opencloudb/"><system><property name="defaultSqlParser">druidparser</property><property name="serverPort">8066</property><property name="managerPort">9066</property></system><user name="test"><property name="password">test123</property><property name="schemas">TESTDB</property></user>
</mycat:server>

​ 这里定义登录Mycat的用户名为test、密码为test123,并且可以访问的schema只有TESTDB。

​ rule.xml里面定义了我们对表进行拆分所涉及的规则定义。我们可以灵活地对表使用不同的分片算法,或者对表使用相同的算法但具体的参数不同。这个文件里面主要有tableRule和function两个标签。在具体的使用过程中可以按照需求添加tableRule和function。此配置文件可以不用修改,使用默认设置即可。

​ 启动Mycat服务,命令为“/usr/local/mycat/bin/mycat console”,如图13-6所示,启动成功。控制台启动这种启动方式在控制台关闭后Mycat服务也将关闭,适合调试使用。

​ Mycat后台启动的命令方式为“/usr/local/mycat/bin/mycat start”,停止Mycat服务的命令为“/usr/local/mycat/bin/mycat stop”,查看Mycat进程的命令为“ps -ef | grep mycat”,查看日志mycat服务启动情况的操作命令为“cat /usr/local/mycat/logs/wrapper.log”。若是Linux版本的MySQL,则需要设置为MySQL大小写不敏感,否则可能会发生表找不到的问题。在MySQL的配置文件my. cnf[mysqld]增加一行“lower_case_table_names=1”。

​ Mycat的默认管理端口为9066,用来接收Mycat监控命令、查询Mycat运行状况、重新加载配置文件等。登录方式类似于MySQL的服务端登录,如图13-7所示,命令格式为“mysql -u Mycat登录用户-p密码-h安装Mycat机器IP地址-P9066-D逻辑库”。

​ Mycat管理命令如图13-8所示。其中,show@@database命令显示Mycat数据库列表,运行结果对应于schema.xml配置文件的schema节点;show@@datanode命令显示Mycat数据节点列表,运行结果对应schema.xml配置文件的dataNode节点;show @@datasource查看数据源状态。

​ Mycat通过默认的数据端口8066接收数据库客户端的访问请求。类似登录数据端口的命令有mysql -utest -ptest -h10.10.75.103 -P8066 –DTESTDB。其中,-h后面是mycat安装的主机地址;-u和-p后面是mycat server.xml中配置逻辑库的用户和密码;-P后面是端口号(注意P是大写);-D后面是mycat server.xml中配置的逻辑库名字。登录后操作,建表并插入一条数据,如图13-9所示。

​ 可以登录到后端的MySQL服务器去验证Mycat是否是按照dataHost配置执行的,如图13-10所示。

13.4 Mycat配置文件详解

​ Mycat通常默认以本地加载XML的方式启动。在Mycat文件的目录中,conf目录存放关于Mycat的配置文件,主要3个需要熟悉:server.xml是Mycat服务器参数调整和用户授权的配置文件;schema.xml是逻辑库定义以及表和分片定义(如分片节点、分片主机等)的配置文件;rule.xml是分片规则的配置文件(分片规则的一些具体参数信息单独存放为文件)。配置文件修改需要重启Mycat或者通过MySQL命令行登录9066管理端口执行reload命令来更新。

13.4.1 schema.xml

​ schema.xml是逻辑库定义以及表和分片定义的重要配置文件。

1.schema标签

<schema name=A04-"TESTDB" checkSQLschema="false" sqlMaxLimit="10">
</schema>

​ schema标签用来定义Mycat实例中的逻辑库。Mycat可以有多个逻辑库,每个逻辑库都有自己的相关配置,可以使用schema标签来划分这些不同的逻辑库。如果不配置schema标签,所有表的配置会属于同一个默认的逻辑库。逻辑库的概念和MySQL中database的概念一样,在查询两个不同逻辑库中的表时,需要切换到该逻辑库下进行。

  • (1)name="TESTDB"是逻辑数据库名,与server.xml配置文件中的schema对应。

  • (2)当checkSQLschema属性值为true时,Mycat会把schema字符去掉。例如,执行语句select *from TESTDB.company,Mycat会把SQL语句修改为select * from company,去掉TESTDB。

  • (3)当sqlMaxLimit属性值设置为某个数值时,若每条执行的SQL语句没有加上limit语句,则Mycat会自动在limit语句后面加上对应的数值。

2.table标签

<table name="travelrecord" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />
<table name="company" primaryKey="ID" type="global" dataNode="dn1,dn2,dn3" />
  • (1)name属性定义的是逻辑表名。

  • (2)dataNode表示存储到哪些节点,多个节点用逗号分隔。节点与下文dataNode标签设置的name属性值是对应的。

  • (3)primaryKey属性是逻辑表对应真实表的主键字。例如,分片的规则是使用非主键进行分片的,那么在使用主键查询的时候就会发送查询语句到所有配置的DN上;如果使用该属性配置真实表的主键,那么Mycat会缓存主键与具体DN的信息,再次使用非主键进行查询的时候就不会进行广播式的查询,而是直接发送语句给具体的DN,但是尽管配置该属性,如果缓存并没有命中,还是会发送语句给具体的DN来获取数据。

  • (4)rule属性定义逻辑表要使用的分片规则名,规则的名字在rule.xml中定义。

  • (5)type属性定义逻辑表的类型,目前逻辑表只有全局表和普通表。type的值是global时代表全局表,不指定global的所有表都为普通表。

3.childTable标签

<table name="customer" primaryKey="ID" dataNode="dn1,dn2" rule="sharding-by-intfile">
<childTable name="c_a" primaryKey="ID" joinKey="customer_id" parentKey="id" />
</table>

​ childTable标签用于定义E-R分片的子表。通过标签上的属性与父表进行关联。

  • (1)name属性:定义子表的名称。

  • (2)joinKey属性:子表中字段的名称,插入子表时会使用这个值查找父表存储的数据节点。

  • (3)parentKey属性:父表中字段的名称。childTable的joinKey会按照父表的parentKey的策略一起切分,当父表与子表进行连接且连接条件是childtable.joinKey=parenttable.parentKey时,不会进行跨库的连接。

4.dataNode标签

<dataNode name="dn1" dataHost="localhost1" database="db1" />

​ dataNode标签定义了Mycat中的数据节点,也就是我们所说的数据分片。一个dataNode标签就是一个独立的数据分片。上述例子表述的意思是使用名字为localhost1数据库实例上的db1物理数据库,组成一个数据分片,最后用dn1来标识这个分片。

  • (1)name属性定义数据节点的名字,唯一,在table标签上用来建立表与分片对应的关系。

  • (2)dataHost属性用于定义该分片属于哪个数据库实例,属性与dataHost标签上定义的name对应。

  • (3)database用于定义该分片属于数据库实例上的哪个具体库。

5.dataHost标签

<dataHost name="localhost1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql"><heartbeat>select user()</heartbeat><writeHost host="hostM1" url="192.168.1.100:3306" user="root" password="123456"><readHost host="hostS1" url="192.168.1.101:3306" user="root" password="123456" /></writeHost>
</dataHost>

​ 这个标签直接定义了具体数据库实例、读写分离配置和心跳语句。

  • (1)name属性用来唯一标示dataHost标签,供上层使用。

  • (2)maxCon属性指定每个读写实例连接池的最大连接。

  • (3)minCon属性指定每个读写实例连接池的最小连接,初始化连接池的大小。

  • (4)balance属性为负载均衡类型。

    • balance=“0”:不开启读写分离机制,所有读操作都发送到当前可用的writeHost上。
    • balance=“1”:全部的readHost与stand by writeHost参与select语句的负载均衡。简单地说,在双主双从模式(M1-S1,M2-S2并且M1 M2互为主备)正常的情况下,M2、S1、S2都参与select语句的负载均衡。
    • balance=“2”:所有读操作都随机地在writeHost、readHost上分发。
    • balance=“3”(1.4之后版本有):所有读请求随机地分发到writeHost对应的readHost执行,writeHost不负担读写压力。
  • (5)writeType表示负载均衡类型。writeType=“0”:所有写操作发送到配置的第一个writeHost,第一个挂了切到生存着的第二个

    • writeHost,重新启动后以切换后的为准,切换记录在配置文件dnindex.properties中。
    • writeType=“1”:所有写操作都随机地发送到配置的writeHost中。1.5以后版本废弃,不推荐。
  • (6)switchType属性:是否自动切换。

    • -1:不自动切换。
    • 1:默认值,自动切换。
    • 2:基于MySQL主从同步的状态决定是否切换,心跳语句为show slave status。
    • 3 : 基 于 MySQL galary cluster 的 切 换 机 制 ( 适 合 集 群 ) , 心 跳 语 句 为 show status like ‘wsrep%’。
  • (7)dbType指定后端连接的数据库类型目前支持二进制的MySQL协议,还有其他使用JDBC连接的数据库,例如MongoDB、Oracle、Spark等。

  • (8)dbDriver指定连接后端数据库使用的driver,目前可选的值有native和JDBC。使用native的话,因为这个值执行的是二进制的MySQL协议,所以可以使用MySQL和maridb;其他类型的则需要使用JDBC驱动来支持。使用JDBC的话,需要将符合JDBC4标准的驱动jar放到mycat\lib目录下。

  • (9)如果配置了tempReadHostAvailable属性,writeHost下面的readHost仍旧可用,默认为0,可配置值为0、1。

6.heartbeat标签

​ 这个标签内指明用于和后端数据库进行心跳检查的语句,例如MySQL可以使用select user()。

7.writeHost /readHost标签

​ 这两个标签都指定后端数据库的相关配置,用于实例化后端连接池。唯一不同的是,writeHost指定写实例、readHost指定读实例。在一个dataHost内可以定义多个writeHost和readHost。如果writeHost指定的后端数据库宕机,那么这个writeHost绑定的所有readHost都将不可用。另外,一个writeHost宕机后,系统会自动检测到,并会切换到备用的writeHost上去。

​ 这两个标签的属性相同,下面一起介绍:

  • 属性host:用于标识不同实例,一般writeHost使用M1、readHost使用S1。

  • 属性url:后端实例连接地址,格式为“Native:地址:端口JDBC:jdbc的url”。

  • 属性名password:后端存储实例需要的密码。

  • 属性名user:后端存储实例需要的用户名字。

  • 属性名weight:权重,配置在readhost中作为读节点的权重。

  • 属性名usingDecrypt:是否对密码加密,默认为0。

13.4.2 server.xml

​ server.xml包含了Mycat的系统配置信息,是Mycat服务器参数调整和用户授权的配置文件。它有两个重要的标签,分别是user、system。

1.user标签

<user name="test"><property name="password">test123</property><property name="schemas">TESTDB</property><property name="readonly">TESTDB</property><property name="benchmark">1000</property><property name="usingDecrypt">0</property>
</user>

​ user标签主要用于定义登录Mycat的用户和权限,如这里定义的登录的用户名为test,也就是连接Mycat的用户名,连接Mycat的密码是test123,可以访问的schema只有TESTDB。要在schema.xml中定义逻辑库TESTDB,则TESTDB必须先在server.xml中定义,否则该用户将无法访问TESTDB。这里用readonly的值(true或false)来限制用户的读写权限;通过设置benchmark属性的值来限制前端的整体连接数量;通过设置usingDecrypt属性的值来开启密码加密功能(默认值为0,表示不开启加密;值为1表示开启加密,同时使用加密程序对密码加密)。

2.privileges标签

​ 对用户的schema以及表进行精细化的DML权限控制:

<privileges check="false">
</privileges>
  • (1)check属性表示是否开启DML权限检查,默认是关闭。

  • (2)DML顺序说明:insert、update、select、delete。

<schema name="db1" dml="0110" ><table name="tb01" dml="0000"></table><table name="tb02" dml="1111"></table>
</schema>

​ 其中,db1的权限是update、select,tb01的权限是什么都不能干,tb02的权限是insert、update、select、delete,其他表默认是update、select。

3.system标签

​ 这个标签内嵌套的所有property标签都与系统配置有关。

  • (1)charset属性

    < property name=“charset”> utf8 < /property>配置字符集,一定要保证Mycat字符集与数据库字符集的一致性。

  • (2)processors属性

    < property name=“processors”> 1 < /property>设置处理线程数量,默认是CPU核心×每个核心运行线程的数量。

  • (3)processorBufferChunk属性

    < property name=“processorBufferChunk”> 4096< /property>表示每次读取流的数量,默认为4096。

  • (4)processorBufferPool属性

    < property name=“processorBufferPool”> 409600 < /property>表示创建共享buffer需要占用的总空间大小。

  • (5)maxPacketSize属性

    < property name=“maxPacketSize”> 16M < /property>指定MySQL协议可以携带的数据的最大长度,默认为16MB。

  • (6)idleTimeout属性

    < property name=“idleTimeout”> 1800000 < /property>指定连接的空闲超时时间,默认为30分钟,单位是毫秒。如果某连接在发起空闲检查下发现距离上次使用超过了空闲时间,那么这个连接会被回收,就是被直接关闭掉。

  • (7)txIsolation属性

    < property name=“txIsolation”> 3 < /property>设置前端连接的初始化事务隔离级别,只在初始化的时候使用,后续会根据客户端传递过来的属性对后端数据库连接进行同步,默认为REPEATED_READ,设置值为数字时默认为3。

READ_UNCOMMITTED=1;
READ_COMMITTED=2;
REPEATED_READ=3;
SERIALIZABLE=4;
  • (8)sqlExecuteTimeout属性

    < property name=“sqlExecuteTimeout”> 300 < /property>定义SQL执行超时的时间,默认时间为300秒,单位为秒。Mycat会检查连接上最后一次执行SQL的时间,若超过这个时间则会直接关闭连接。

  • (9)serverPort属性

    < property name=“serverPort”> 8066 < /property>定义Mycat的使用端口,默认值为8066。

  • (10)managerPort属性

    < property name=“managerPort”> 9066 < /property>定义Mycat的管理端口,默认值为9066。

​ 以上列举的属性仅仅是一部分,可以配置的变量很多。system标签下的属性一般是在上线后根据实际运行的情况分析调优时进行修改。

4.firewall标签

​ 这个标签用于防火墙的设置,就是在网络层对请求的地址进行限制,主要是从安全角度来保证Mycat不被匿名IP进行访问。

<firewall><whitehost><host host="127.0.0.1" user="mycat"/><host host="127.0.0.2" user="mycat"/></whitehost><blacklist check="false"></blacklist>
</firewall>

​ 设置很简单,很容易理解:开启防火墙,只有白名单(设置了白名单)的连接才可以进行连接。

13.4.3 rule.xml

​ rule.xml是分片规则的配置文件,定义了对表进行拆分所涉及的规则定义。我们可以灵活地对表使用不同的分片算法,或者对表使用相同的算法但具体的参数不同。包含的标签有function和tableRule。

1.function标签

<function name="rang-mod" class="org.opencloudb.route.function.PartitionByRangeMod"><property name="mapFile">partition-hash-int.txt</property>
</function>
  • name属性指定算法的名字。

  • class属性对应具体的分片算法,需要指定算法具体的类名字。

  • property属性根据算法的要求指定,为具体算法需要用到的一些属性。

2.tableRule标签

​ 这个标签定义表规则。

<tableRule name="auto-sharding-rang-mod"><rule><columns>id</columns><algorithm>rang-mod</algorithm></rule>
</tableRule>
  • name属性指定分片唯一算法的名称,用于标识不同的表规则。

  • 内嵌的rule标签指定对物理表中的哪一列进行拆分和使用什么路由算法。

​ columns内指定要拆分的列名字。algorithm指定实现算法的名称,对应的是function标签中的name属性。连接表规则和具体路由算法。多个表规则可以连接到同一个路由算法上。

3.分片策略

​ 简单来说,我们可以将数据的水平切分理解为是按照数据行切分,就是将表中的某些行切分到一个数据库、另外的某些行切分到其他数据库中。其中,选择合适的切分策略至关重要,因为它决定了后续数据聚合的难易程度。典型的分片策略包括:

  • 按照用户主键ID求模,将数据分散到不同的数据库,具有相同数据用户的数据都被分散到一个库中;
  • 按照日期将不同月甚至日的数据分散到不同的库中;
  • 按照某个特定的字段求摸,或者根据特定范围段分散到不同的库中。

​ Mycat的分片策略很丰富,是超出预期的,也是Mycat的一大亮点。大体分片规则(还有一些其他分片方式,这里不全部列举)如下:

  • (1)取模分片:mod-long,根据id进行十进制求模计算。

  • (2)分片枚举:sharding-by-intfile,通过在配置文件中配置可能的枚举id来指定数据分布到不同物理节点上。

  • (3)范围分片:auto-sharding-long,适用于明确知道分片字段的某个范围属于哪个分片。

  • (4)范围求模算法:先进行范围分片,计算出分片组,组内再求模,综合了范围分片和求模分片的优点。

  • (5)字符串hash解析算法:sharding-by-stringhash ,截取字符串中的int数值hash分片。

  • (6)按日期(天)分片:sharding-by-date,除了columns标识将要分片的表的字段,sBeginDate为开始日期,sEndDate为结束日期。如果配置了sEndDate,则代表数据达到了这个日期的分片后会重复从开始分片插入。

  • (7)按单月小时拆分:sharding-by-hour,表示单月内按照小时拆分,最小粒度是小时,一天最多可以有24个分片,最少1个分片,下个月从头开始循环,每个月末需要手动清理数据。

  • (8)自然月分片:sharding-by-month,使用场景为按月份列分区,每个自然月一个分片。

  • (9)日期范围hash算法:思想方法与范围求模一致,由于日期取模的方法会出现数据热点问题,因

此先根据日期分组再根据时间hash,使得短期内数据分布得更均匀。

4.分片策略使用示例

  • (1)分片策略使用示例一
<tableRule name="rule1"><rule><columns>id</columns><algorithm>mod-long</algorithm></rule>
</tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod"><!-- how many data nodes --><property name="count">2</property>

​ 定义分片策略rule1,该策略对分表的id进行mod2除法,对模2算法的结果进行分库。

  • (2)分片策略使用示例二

    要求部门号为10的数据被分发到第一个节点中、部门号为20的数据被分发到第二个节点中、部门号为30的数据被分发到第三个节点中,需定义3个值,分片规则均是在rule.xml中定义。

​ 首先定义规则tableRule标签:

<tableRule name="sharding-by-intfile-TESTDB-employee"><rule><columns>deptno</columns><algorithm>hash-int-TESTDB-employee</algorithm></rule>
</tableRule>

​ 接着定义规则function标签:

<function name="hash-int-TESTDB-employee"          class="org.opencloudb.route.function.PartitionByFileMap"<property name="mapFile">partition-hash-int-TESTDB-employee.txt</property><property name="type">0</property><property name="defaultNode">0</property>
</function>

​ 这里type默认值为0(0表示Integer,非零表示String)。defaultNode表示默认节点,小于0时不设置默认节点,大于等于0时设置默认节点。默认节点的作用是在枚举分片时碰到不识别的枚举值就路由到默认节点。如果不配置默认节点,碰到不识别的枚举值就会报错。

​ 最后创建规则文件partition-hash-int-TESTDB-employee.txt。在conf目录路径下创建该文件,定义枚举的规则,内容如下:

10=0
20=1
30=2

13.5 Mycat分库分表实战【*】

​ 首先在Mycat的conf目录下编辑service.xml、rule.xml、schema.xml三个文件:server.xml配置Mycat用户名和密码使用的逻辑数据库等;rule.xml配置路由策略,主要分片的片键、拆分的策略(取模还是按区间划分等);schema.xml文件主要配置数据库的信息,例如逻辑数据库名称、物理上真实的数据源以及表和数据源之间的对应关系和路由策略等。

​ 演示案例场景是分别创建了3个数据库db01、db02、db03,有4张表users、item、customer、orders,并分别在这3个数据库下都创建了相同的4张表。建表语句如下:

CREATE TABLE users (id INT NOT NULL AUTO_INCREMENT,name varchar(50) NOT NULL default '',indate DATETIME NOT NULL default '0000-00-00 00:00:00',PRIMARY KEY (id)
)AUTO_INCREMENT= 1 ENGINE=InnoDB DEFAULT CHARSET=utf8;CREATE TABLE item (id INT NOT NULL AUTO_INCREMENT,value INT NOT NULL default 0,indate DATETIME NOT NULL default '0000-00-00 00:00:00',PRIMARY KEY (id)
)AUTO_INCREMENT= 1 ENGINE=InnoDB DEFAULT CHARSET=utf8;CREATE TABLE customer(id int primary key,name varchar(30));CREATE TABLE orders(id int primary key,name varchar(30),customer_id int,
constraint fk_companyid foreign key(customer_id)references customer(id));

​ 分库分表测试所要的效果是:users表存储在数据库db01,而item表的数据分布存储在数据库db01和db02。customer和orders表也是分布存储在数据库db01、db02、db03中。与item表的分布不同的是,对于customer表id属于0~1000范围内的在db01.customer表中, id属于10002000的在db02.customer表中,id属于20003000的在db03.customer表中。验证Mycat支持ER分片,即子表的记录与所关联的父表记录存放在同一个数据分片上,orders表依赖父表customer进行分片。

​ server.xml配置如图13-11所示,数据库的名称为TESTDB,用户名为test,密码为test123,服务端口为8066。

​ 根据要求,Mycat的schema.xml配置内容如图13-12所示。

​ 分片规则rule.xml的配置如图13-13所示。Item表的分片规则为rule1,分片算法为取模分片mod-long;表customer的分片规则是auto-sharding-long-custom,分片算法为rang-long。

​ 创建规则文件auto-sharding-long-custom.txt,如图13-14所示,id属于01000范围内的在分区1里、10002000的在分区2里、2000~3000的在分区3里。

​ 确保Mycat正常启动,如图13-15所示。

​ 执行命令登录数据端口,命令是“mysql -utest -ptest123 -h10.10.75.102 -P8066 -DTESTDB”,如图13-16所示,可以看到逻辑库、逻辑表。

​ 往表中插入数据,SQL语句如下:

insert into users(name,indate) values('LL',now());
insert into users(name,indate) values('HH',now());
insert into item(id,value,indate) values(1,100,now());
insert into item(id,value,indate) values(2,100,now());
insert into customer(id,name) values(999,'dan');
insert into customer(id,name) values(1000,'jiao');
insert into customer(id,name) values(1003,'song');
insert into customer(id,name) values(2002,'yang');
insert into orders(id,name,customer_id) values(1,'mirror',999);
insert into orders(id,name,customer_id) values(2,'banana',2002);
insert into orders(id,name,customer_id) values(3,'apple',1003);
insert into orders(id,name,customer_id)values(4,'pear',2002);

​ 查询各表,如图13-17所示,显示数据已正常插入。

​ 可以在Mycat上正常地联合查询,如图13-18所示。

​ 切换到后端的MySQL服务器,验证结果,如图13-19所示,users表只存储在数据库db01里。

​ item表的数据只分布存储在数据库db02和db03中,数据库db01中没有item表的数据,如图13-20所示。

​ customer表中id属于01000范围内的在db01.customer表中,id属于10002000的在db02.customer表中,id属于2000~3000的在db03.customer表中,如图13-21所示。

​ Mycat支持ER分片,orders表依赖父表customer进行分片,即子表的记录与所关联的父表记录存放在 同一个数据分片上。如图13-22所示,orders表的customer_id列对应的customer表的id列属于哪个分片,orders表的那条记录就在哪个分片内。

以上演示验证了Mycat中间件强大的分库分表功能。

13.6 Mycat读写分离实战【*】

​ 在很多系统中,读操作的比例远远大于写操作,所以数据库读写分离对于大型系统或者访问量很高的互联网应用来说是必不可少的一个重要功能。对于MySQL来说,标准的读写分离是主从模式,一个主节点后面跟着多个从节点,从节点上的从数据库提供读操作,主从节点通过数据库主从复制技术来保持同步。从节点的数量取决于系统的压力,通常是1~3个读节点的配置。利用Mycat很容易搭建读写分离,如图13-23所示,当MySQL按照之前的主从复制方式配置好集群以后开启Mycat的读写分离机制,所有的读请求都将分发到writeHost对应的readHost上执行,writeHost不负担读压力。主流的读写分离是主从复制和galera cluster,这里需要先配置MySQL的主从复制再配置Mycat读写分离

1.Mycat读写分离配置

​ 后台数据库的读写分离和负载均衡由schema文件dataHost标签的balance属性控制。配置Mycat对MySQL主从复制的读写分离schema.xml文件,内容如下:

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://org.opencloudb/"><schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"></schema>
<dataNode name="dn1" dataHost="dh1" database="mldn"/>
<dataHost name="localhost1" balance="1" maxCon="1000" minCon="10"
writeType="0" switchType="-1" slaveThreshold="100" dbType="mysql" dbDriver="native"><heartbeat>show slave status</heartbeat><writeHost host="hostM1" url="10.10.75.100:3306" password="123@abc" user="root"><readHost host="hostS1" url="10.10.75.101:3306" user="root" password="123@abc" /><readHost host="hostS2" url="10.10.75.102:3306" password="123@abc" user="root"/></writeHost></dataHost>
</mycat:schema>

​ 这里两个从节点hostS1、hostS2与一个主节点hostM1组成了一主两从的读写分离模式。

​ 在该配置文件中,balance=“1”,意味着作为stand by writeHost的hostS1和hostS2将参与select语句的负载均衡,实现了主从的读写分离;switchType=’-1’,意味着当主节点挂掉的时候不进行自动切换,即hostS1和hostS2并不会被提升为主节点,仍然只提供读的功能。这就避免了将数据写进从节点的可能性,毕竟单纯的MySQL主从集群并不允许将数据读进从节点中,除非配置的是双主。

2.验证读写分离

​ 编辑/usr/local/mycat/conf/log4j.xml,通过将log日志级别改为DEBUG来确认基于Mycat是不是真的做到读写分离(可以在正式上生产环境前再改成INFO级别)。通过tail命令从mycat.log日志中查看执行的节点就可以知道是不是自己设置的读节点,如图13-24所示。

《MySQL性能优化和高可用架构实践》阅读总结相关推荐

  1. 【读书】《非暴力沟通》

    得益于十点读书,在2月中完成开年来的第二本书籍阅读.本书作者马歇尔.卢森堡博士是国际性缔造和平组织非暴力沟通中心(CNVC)的创始人和教育服务主管,马歇尔.卢森堡博士由于在促进人类和谐共处方面的突出成 ...

  2. 《非暴力沟通》读书笔记

    <非暴力沟通>读书笔记 [本书作者] 马歇尔·卢森堡,卡尔·罗杰斯的弟子,同时其思想深受"圣雄"甘地和存在主义哲学大师马丁·布伯的影响. [本书要解答的问题] 是什么使 ...

  3. 摘录与感想:非暴力沟通

    摘录与感想:非暴力沟通 1.观察2.感受3.需要4.请求 首先,留意发生的事情.我们此刻观察到什么?不管是否喜欢,只是说出人们所做的事情.要点是,清楚地表达观察结果,而不判断或评估.接着,表达感受,例 ...

  4. 《非暴力沟通》:有些话真的可以好好说

    本文结构 - 前言 - 非暴力沟通简介 - 01 观察和评价 - 02 体会表达感受 - 03 感受背后的需求 - 04 提出请求 - 需求驱动 - 好好说话的力量 本文共计:3000字4图 预计阅读 ...

  5. 《非暴力沟通》- 笔记

    非暴力沟通的核心:当我们情绪受伤的时候,都是某些需求没有满足.你现在最要做的是发现需求,而不是发泄情绪.情绪是双刃剑,说出去很爽快,但会造成不好的后果. 非暴力沟通的步骤: 先说事实 再说感受 再说自 ...

  6. 2016年第7本:非暴力沟通

    周首送我的这本书<非暴力沟通>(NVC,Nonviolent Communication),是马歇尔·卢森堡博士发明的一种沟通方式,全书强调了四要素,共8个字:观察.感受.需要.请求.就是 ...

  7. 《非暴力沟通》听书心得

    沟通很多时候也是一门艺术,不是吗? 1.  一句话总结 非暴力沟通方法可以概况为四个字:观.感.需.求 观(观察).感(感受).需(需求).求(请求) 2.   精髓含义 观:仔细观察当下,而不要和& ...

  8. 【读书笔记】非暴力沟通

    文章目录 背景 理论 感受 反省 总结 推荐 背景 这个季度看了几本书,比如<一个人的朝圣>.<呼兰河传>.<元红>等等,女友也推荐了一本书给我,书名是<非暴 ...

  9. 读《非暴力沟通》马歇尔·卢森堡

    前言 非暴力生活的一个关键就是:感激生活的赐予,而不贪心 为了清晰的表达感受,我们编制了以下的词汇表 表达我们的需要得到满足时的感受 兴奋/喜悦/欣喜/甜蜜/精力充沛/兴高采烈/ 感激/感动/乐观/自 ...

  10. 《非暴力沟通》读后感

    最近几天偶然了发现桌角的kindle,才发现原来已经好久没有碰过她了.打开封盖,还有百分之60+的电量,着实让我一惊. 为了不让花出去的钱白白吃土,于是每天晚上睡觉前,就打开kindle看一会儿, 最 ...

最新文章

  1. 使用sigaction处理内核信号
  2. web服务器time_wait值过高解决方案
  3. Kubernetes的ConfigMap说明
  4. API通常的url语法
  5. linux学习之lvm-逻辑卷管理器
  6. PL/SQL远程连接Oracle数据库服务器
  7. 博弈论1(正则型博弈)
  8. [译]使用YUI 3开发Web应用的诀窍
  9. Windows下Perl环境安装和使用
  10. 服务器硬盘坏道修复教程视频,坏道和坏块什么区别?硬盘高级修复教程来了
  11. DSDT与SSDT提取
  12. 手把手教你使用--常用模块--HC05蓝牙模块,无线蓝牙串口透传模块,(实例:手机蓝牙控制STM32单片机点亮LED灯)
  13. postgresql standard_conforming_strings参数
  14. 【技术邻】基于Ansys Icepak的散热器优化
  15. 【爬虫】从零开始使用 Scrapy
  16. Keras: 创建多个输入以及混合数据输入的神经网络模型
  17. mouseover和mouseenter的异同
  18. dB,dBi, dBd, dBc,dBm,dBw释义及区别
  19. 入门学术研究和学术论文
  20. linux下编译安装ntfs,内核编译安装 (用NTFS模块)

热门文章

  1. java算法优化_Java学习笔记---Java简单的代码算法优化(例)
  2. 写一手好SQL很有必要
  3. vue实现调查问卷一页一题,上一题下一题形式
  4. java解析ceb文件_读取文件夹内容解析为Tree结构
  5. Ford-Fulkerson 标号法求网络最大流
  6. 视频会议十大开源项目
  7. 正确使用GPU服务器gpu服务器和普通服务器的不同之处
  8. 在 Linux CentOS7 上安装 Maven
  9. 支持DoH的DNS服务器,谷歌公共DNS正式支持DoH加密 更安全并且不影响速度
  10. HNU--计算机网络实验2