文章目录

  • 一、Mysql架构
    • 1.1 查询语句的执行过程
      • 1.1.1 连接器
      • 1.1.2 查询缓存
      • 1.1.3 分析器
      • 1.1.4 优化器
      • 1.1.5 执行器
      • 1.1.6 存储引擎
      • 1.1.7 执行引擎,返回结果
    • 1.2 更新语句的执行过程
    • 1.3 MySQL数据存储文件
      • 1.3.1 MyISAM
      • 1.3.2 InnoDB
    • 1.4 MySQL的一些参数
  • 二、数据库优化
    • 2.1 数据库结构优化
    • 2.2 大表怎么优化
      • 2.2.1 垂直分区
      • 2.2.2 水平分区
      • 2.2.3 分库分表后面临的问题
      • 2.2.4 怎么解决分库分表后带来的问题
    • 2.3 分库分表之后,ID主键如何处理
    • 2.4 优化数据库的方法(概述)
    • 2.5 Mysql数据库的优化(语句和参数)
      • 2.5.1 sql的优化
      • 2.5.2 合理设置mysql的部分参数
    • 2.6 Mysql性能优化(架构)
      • 2.6.1 连接——配置优化
      • 2.6.2 缓存——架构优化
    • 2.7 分表分库
      • 2.7.1 垂直分表
      • 2.7.2 水平拆分
    • 2.8 数据库优化的相关问题
      • 2.8.1 MySQL数据库cpu飙升到500%的话怎么处理
      • 2.8.2 表中有大字段X(例如:text 类型),且字段X不会经常更新,以读为主,将该字段拆成子表好处是什么
      • 2.8.3 一些数据库设计的注意事项
  • 三、Mysql日志
    • 3.1 Redo日志和Undo日志
    • 3.2 Binlog日志
    • 3.3 数据更新的流程
    • 3.4 binlog和redolog的区别
    • 3.5 MySQL记录binlog的方式主要包括三种模式?每种模式的优缺点是什么?
  • 四、主从复制
    • 4.1 Mysql的复制原理以及流程
    • 4.2 Mysql支持的复制类型
      • 4.2.1 基于语句的复制(statment)
      • 4.2.2 基于行的复制(row)
      • 4.2.3 混合类型的复制(mixed)
      • 4.2.4 Binlog基本配置与格式设定
    • 4.3 主从复制的几个问题
    • 4.4 Mysql主从形式
    • 4.5 Mysql主从同步延时分析
    • 4.6 主从复制示例
      • 4.6.1 配置主库
      • 4.6.2 配置从库
  • 五、数据库优化的相关问题
    • 5.1 读写分离有哪些解决方案

本系列文章:
  Mysql(一)三大范式、数据类型、常用函数、事务
  Mysql(二)Mysql SQL练习题
  Mysql(三)索引、视图、存储过程、触发器、分区表
  Mysql(四)存储引擎、锁
  Mysql(五)Mysql架构、数据库优化、主从复制
  Mysql(六)慢查询、执行计划、SQL语句优化

一、Mysql架构

1.1 查询语句的执行过程

  Mysql架构:

  大体来说,MySQL可以分为Server层和存储引擎层两部分。

  • 1、Server层
      Server层包括连接器、查询缓存、分析器、优化器、执行器等,提供了Mysql Server 数据库所有逻辑功能,涵盖MySQL的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图、函数等。
  • 2、存储引擎层
      存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持InnoDB、MyISAM、Memory等多个存储引擎。
     存储引擎是MySQL中具体与文件打交道的子系统,MySQL区别于其他数据库的最重要特点是其插件式的表存储引擎,其根据文件访问层抽象接口来定制一种文件访问的机制(该机制叫存储引擎)。物理文件包括:redolog、undolog、binlog、errorlog、querylog、slowlog、data、index等。也就是说,执行create table建表的时候,如果不指定引擎类型,默认使用的就是InnoDB。

1.1.1 连接器

  Mysql 服务器默认监听端口是3306
  连接器负责跟客户端建立连接、获取权限、维持和管理连接。连接命令常规写法:

 mysql -h$ip -P$port -u$user -p

  连接命令中的Mysql是客户端工具,用来跟服务端建立连接。在完成TCP握手后,连接器就要开始认证身份,这个时候用的就是输入的用户名和密码。
  如果用户名密码认证通过,连接器会到权限表里面查出你拥有的权限。之后,这个连接里面的权限判断逻辑,都将依赖于此时读到的权限。这就意味着,一个用户成功建立连接后,即使你用管理员账号对这个用户的权限做了修改,也不会影响已经存在连接的权限。修改完成后,只有再新建的连接才会使用新的权限设置。

  • 1、show processlist
      连接完成后,如果你没有后续的动作,这个连接就处于空闲状态,你可以在show processlist命令中看到它:

      一些常见的连接状态:
状态 含义
Sleep 线程正在等待客户端,以向它发送一个新语句
Query 线程正在执行查询或往客户端发送数据
Locked 该查询被其他查询锁定
Copying to tmp table on disk 临时结果集合大于tmp_table_size,线程把临时表从存储器内部格式改变为磁盘模式,以节约存储器
Sending data 线程正在为Select语句处理行,同时正在向客户端发送数据
Sorting for group 线程正在进行分类,以满足Group by要求
Sorting for order 线程正在进行分类,以满足Order by要求
  • 2、连接数
      查看 MySQL 当前有多少个连接命令:
 show global status like 'Thread%';

  结果示例:

  参数含义:

Threads_cached:缓存中的线程连接数。
Threads_connected:当前打开的连接数。
Threads_created:为处理连接创建的线程数。
Threads_running:非睡眠状态的连接数,通常指并发连接数。

  MySQL服务允许的最大连接数,在5.7版本中默认是151个,最大可以设置成16384(214)。查看命令是:

 show variables like 'max_connections';
  • 3、长连接
      MySQL是支持多种通信协议的,可以使用同步/异步的方式,支持长连接/短连接。使用异步方式的话,服务端带来巨大的压力(一个连接就会创建一个线程,线程间切换会占用大量CPU资源);另外异步通信还带来了编码的复杂度,所以一般不建议使用。如果要异步,必须使用连接池,排队从连接池获取连接而不是创建新连接。
      MySQL既支持短连接,也支持长连接。短连接就是操作完毕以后,马上close掉。长连接可以保持打开,减少服务端创建和释放连接的消耗,后面的程序访问的时候还可以使用这个连接。一般我们会在连接池中使用长连接。
      客户端如果太长时间没动静,连接器就会自动将它断开。这个时间是由参数wait_timeout控制的,默认值是8小时

  数据库里面,长连接是指连接成功后,如果客户端持续有请求,则一直使用同一个连接。短连接则是指每次执行完很少的几次查询就断开连接,下次查询再重新建立一个。建立连接的过程通常是比较复杂的,所以建议尽量使用长连接

  全部使用长连接后,有些时候MySQL占用内存涨得特别快,这是因为MySQL在执行过程中临时使用的内存是管理在连接对象里面的。这些资源会在连接断开的时候才释放。所以如果长连接累积下来,可能导致内存占用太大,被系统强行杀掉(OOM),从现象看就是MySQL异常重启了。两种解决方案:

  1. 定期断开长连接。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后要查询再重连。
  2. 如果用的是MySQL 5.7或更新版本,可以在每次执行一个比较大的操作后,通过执行 mysql_reset_connection来重新初始化连接资源。这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态。
  • 4、半双工通信方式
      MySQL支持哪些通信协议呢?第一种是Unix Socket,默认是使用该协议,如果指定-h参数,就会用第二种方式,TCP/IP协议:
 mysql -h192.168.8.211 -uroot -p123456

  编程语言的连接模块都是用TCP协议连接到MySQL服务器的

 MySQL使用了半双工的通信方式,所以客户端发送SQL语句给服务端的时候,(在一次连接里面)数据是不能分成小块发送的,不管你的SQL语句有多大,都是一次性发送。另一方面,对于服务端来说,也是一次性发送所有的数据,不能因为你已经取到了想要的数据就中断操作,这个时候会对网络和内存产生大量消耗。
 所以,一定要在程序里面避免不带limit的这种操作,比如一次把所有满足条件的数据全部查出来,一定要先count 一下。如果数据量的话,可以分批查询。

1.1.2 查询缓存

  MySQL 的缓存默认是关闭的
  连接建立完成后,就可以执行select语句了。执行逻辑就会来到第二步:查询缓存。MySQL拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句。之前执行过的语句及其结果可能会以key-value对的形式,被直接缓存在内存中。key是查询的语句,value是查询的结果。如果你的查询能够直接在这个缓存中找到key,那么这个value就会被直接返回给客户端。如果语句不在查询缓存中,就会继续后面的执行阶段。执行完成后,执行结果会被存入查询缓存中。
  但是,查询缓存往往弊大于利查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。因此,对于更新压力大的数据库来说,查询缓存的命中率会非常低。因此,MySQL 8.0版本直接将查询缓存的整块功能删掉了。
  查询缓存相关的参数:

  可以通过设置合适的query_cache_min_res_unit来减少碎片。
  可以通过以上查询状态的命令查看Qcache_hits,该值表示缓存命中率。如果缓存命中率特别低的话,我们还可以通过query_cache_size = 0或者query_cache_type来关闭查询缓存。

1.1.3 分析器

  如果没有命中查询缓存,就要开始真正执行语句了。分析器先会做“词法分析”。根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个SQL语句是否满足MySQL语法。语法分析之后是语义解析,即检查表名、列名等是否存在,是否正确。
  这一步主要做的事情是对语句基于SQL语法进行词法和语法分析和语义的解析。词法分析就是把一个完整的SQL语句打碎成一个个的单词。接下来就是语法分析,语法分析会对SQL做一些语法检查,比如单引号有没有闭合,然后根据MySQL定义的语法规则,根据SQL语句生成一个数据结构。这个数据结构我们把它叫做解析树

  在解析的时候报错,解析SQL的环节里面有个预处理器。它会检查生成的解析树,解决解析器无法解析的语义。比如,它会检查表和列名是否存在,检查名字和别名,保证没有歧义。预处理之后得到一个新的解析树。

1.1.4 优化器

  优化器最终会把解析树变成一个查询执行计划,查询执行计划是一个数据结构。MySQL 提供了一个执行计划的工具。我们在 SQL 语句前面加上 EXPLAIN,就可以看到执行计划的信息。
  优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序。示例:

 select * from t1 join t2 using(ID)  where t1.c=10 and t2.d=20;

既可以先从表t1里面取出c=10的记录的ID值,再根据ID值关联到表t2,再判断t2里面d的值是否等于20。
也可以先从表t2里面取出d=20的记录的ID值,再根据ID值关联到t1,再判断t1里面c的值是否等于10。

  两种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择使用哪一个方案。优化器阶段完成后,这个语句的执行方案就确定下来了,然后进入执行器阶段。

1.1.5 执行器

  开始执行的时候,要先判断一下你对这个表T有没有执行查询的权限,如果没有,就会返回没有权限的错误。如果命中查询缓存,会在查询缓存放回结果的时候,做权限验证。查询也会在优化器之前调用precheck验证权限)。示例:

 select * from T where ID=10

  如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。比如我们这个例子中的表T中,ID字段没有索引,那么执行器的执行流程是这样的:

  1. 调用InnoDB引擎接口取这个表的第一行,判断ID值是不是10,如果不是则跳过,如果是则将这行存在结果集中;
  2. 调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
  3. 执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。

  对于有索引的表,执行的逻辑也差不多。第一次调用的是“取满足条件的第一行”这个接口,之后循环取“满足条件的下一行”这个接口,这些接口都是引擎中已经定义好的。

1.1.6 存储引擎

 数据库的表在存储数据的同时,还要组织数据的存储结构,这个存储结构就是由我们的存储引擎决定的,所以我们也可以把存储引擎叫做表类型。在MySQL里面,支持多种存储引擎,他们是可以替换的,所以叫做插件式的存储引擎。
 MyISAM和InnoDB是我们用得最多的两个存储引擎,在MySQL 5.5版本之前,默认的存储引擎是MyISAM,5.5版本之后默认的存储引擎改成了InnoDB。

  • 1、MyISAM( 3 个文件)
      应用范围比较小。表级锁定限制了读/写的性能,因此在Web和数据仓库配置中,它通常用于只读或以读为主的工作
     特点:

1.支持表级别的锁(插入和更新会锁表)。不支持事务。
2.拥有较高的插入(insert)和查询(select)速度。
3.存储了表的行数(count 速度更快)。

  • 2、InnoDB( 2 个文件)
      InnoDB是一个事务安全(与ACID兼容)的MySQL存储引擎,它具有提交、回滚和崩溃恢复功能来保护用户数据。InnoDB行级锁(不升级为更粗粒度的锁)和Oracle风格的一致非锁读提高了多用户并发性和性能。InnoDB将用户数据存储在聚集索引中,以减少基于主键的常见查询的I/O。为了保持数据完整性,InnoDB还支持外键引用完整性约束。
      特点:
  1. 支持事务,支持外键,因此数据的完整性、一致性更高。
  2. 支持行级别的锁和表级别的锁
  3. 支持读写并发,写不阻塞读(MVCC)。
  4. 特殊的索引存放方式,可以减少IO,提升查询效率。

  如何选择存储引擎?

  1. 如果对数据一致性要求比较高,需要事务支持,可以选择InnoDB
  2. 如果数据查询多更新少,对查询性能要求比较高,可以选择MyISAM
  3. 如果需要一个用于查询的临时表,可以选择Memory。

1.1.7 执行引擎,返回结果

1.2 更新语句的执行过程

  更新语句执行流程如下:分析器、权限校验、执行器、引擎、 redo log ( prepare状态)、binlog 、 redo log ( commit状态) 。
  举个例子:

 update user set name = '张三' where id = 1;
  • 1、先查询到id为1的记录,有缓存会使用缓存。
  • 2、拿到查询结果,将name更新为张三,然后调用引擎接口,写入更新数据,innodb引擎将数据保存在内存中,同时记录redo log ,此时redo log进入prepare状态。
  • 3、执行器收到通知后记录binlog ,然后调用引擎接口,提交redo log为commit状态。
  • 4、更新完成。

  为什么记录完redo log ,不直接提交,而是先进入prepare状态?假设先写redo log直接提交,然后写binlog ,写完redo log后,机器挂了, binlog日志没有被写入,那么机器重启后,这台机器会通过redo log恢复数据,但是这个时候binlog并没有记录该数据,后续进行机器备份的时候,就会丢失这一条数据,同时主从同步也会丢失这一条数据。

1.3 MySQL数据存储文件

  每张InnoDB的表有两个文件(.frm 和.ibd),MyISAM的表有三个文件(.frm、.MYD、.MYI)。

  .frm是MySQL里面表结构定义的文件,选用任何一个存储引擎都会生成。

1.3.1 MyISAM

  在MyISAM里面,另外有两个文件:一个是.MYD文件,D代表Data,是MyISAM的数据文件,存放数据记录。一个是.MYI文件,I代表Index,是MyISAM的索引文件,存放索引
  MyISAM的B+Tree里面,叶子节点存储的是数据文件对应的磁盘地址。所以从索引文件.MYI中找到键值后,会到数据文件.MYD中获取相应的数据记录。

1.3.2 InnoDB

  在InnoDB里面,它是以主键为索引来组织数据的存储的,所以索引文件和数据文件是同一个文件,都在.ibd文件里面。在InnoDB的主键索引的叶子节点上,它直接存储了我们的数据。

1.4 MySQL的一些参数

二、数据库优化

  • 为什么要数据库优化?
  1. 系统的吞吐量瓶颈往往出现在数据库的访问速度上。
  2. 随着应用程序的运行,数据库的中的数据会越来越多,处理时间会相应变慢。
  3. 数据是存放在磁盘上的,读写速度无法和内存相比。

  因此数据库的优化原则:减少系统瓶颈,减少资源占用,增加系统的反应速度。

2.1 数据库结构优化

  数据库结构优化需要考虑数据冗余、查询和更新的速度、字段的数据类型是否合理等多方面的内容。

  • 1、将字段很多的表分解成多个表
      对于字段较多的表,如果有些字段的使用频率很低,可以将这些字段分离出来形成新表
      因为当一个表的数据量很大时,会由于使用频率低的字段的存在而变慢。
  • 2、增加中间表
      对于需要经常联合查询的表,可以建立中间表以提高查询效率
      通过建立中间表,将需要通过联合查询的数据插入到中间表中,然后将原来的联合查询改为对中间表的查询。
  • 3、增加冗余字段
      设计数据表时应尽量遵循范式理论的规约,尽可能的减少冗余字段,让数据库设计看起来精致、优雅。但是,合理的加入冗余字段可以提高查询速度。
      冗余字段:在设计数据库时,某一字段属于一个表,但它又同时出现在另一个或多个表,且完全等同于它在其本来所属表的意义表示,那么这个字段就是一个冗余字段
      冗余字段的值在一个表中修改了,就要想办法在其他表中更新,否则就会导致数据不一致的问题
      冗余字段会导致一些问题,比如,冗余字段的值在一个表中被修改了,就要同步关联的表,否则会导致数据不一致。这要根据实际情况,平衡数据库性能,进行冗余字段的设计。
  • 4、所有字段均定义为NOT NULL,除非真的需要存储null
  • 5、提前做好数据量的预估,进行分表设计
      不要等需要拆分时再拆,一般把表的数据量控制在千万级别。当单表数据量达到一定程度时(MySQL5.x时代的性能拐点则为1KW - 2KW行级别,具体需根据实际情况测试),为了提升性能,最为常用的方法就是分表。分表的策略可以是垂直拆分(比如:不同订单状态的订单拆分到不同的表),也可以是水平拆分(比如:按月将订单拆分到不同表)。如果在业务层分表,会将逻辑变得复杂,而且分散。可以引入分表的中间件屏蔽分表后的细节,让业务层像查询单表一样查询分表后的数据。比如Mycat。(访问量不大,但是表数据很多的表,我们可以采取分区表,实现起来也比较简单)
  • 6、合理的设置主键和索引
  • 7、一般情况下都是采用业务主键
      一般情况下都是采用业务主键,主键分自增主键和业务主键:
  1. 自增主键
      写入、查询效率和磁盘利用率都高,但每次查询都需要两级索引,因为线上业务不会有直接使用主键列的查询。
  2. 业务主键
      写入、查询效率和磁盘利用率都低,但可以使用一级索引,依赖覆盖索引的特性,某些情况下在非主键索引上也可以实现1次索引完成查询

2.2 大表怎么优化

  当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施:

  • 1、限定数据的范围
      务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,可以控制在一个月的范围内;
  • 2、读写分离
      经典的数据库拆分方案,主库负责写,从库负责读;
  • 3、缓存
      使用MySQL的缓存,另外对重量级、更新少的数据可以考虑使用应用级别的缓存;
  • 4、分库分表
      主要有垂直分表和水平分表。
  • 5、优化SQL
      建合适的索引,优化查询SQL。

2.2.1 垂直分区

  比如有些表会有大量的属性,将一些相关的属性拆分到一张单独的表。
  根据数据库里面数据表的相关性进行拆分。简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。
  垂直分表:通常是按照业务功能的使用频次,把主要的、热门的字段放在一起做为主要表;然后把不常用的,按照各自的业务属性进行聚集,拆分到不同的次要表中;主要表和次要表的关系一般都是一对一的。冷数据放到主要表中,热数据放到次要表中。
  垂直分表:把主键和一些列放在一个表,然后把主键和另外的列放在另一个表中, 示例:

  • 垂直拆分的优点
  1. 可以使得行数据变小,在查询时减少读取的Block数,减少I/O次数
  2. 垂直分区可以简化表的结构,易于维护
  • 垂直分表的适用场景
  1. 如果一个表中某些列常用,另外一些列不常用
  2. 可以使数据行变小,一个数据页能存储更多数据,查询时减少I/O次数。
  • 垂直拆分的缺点
  1. 有些分表的策略基于应用层的逻辑算法,一旦逻辑算法改变,整个分表逻辑都会改变,扩展性较差。
  2. 对于应用层来说,逻辑算法增加开发成本。
  3. 管理冗余列,查询所有数据需要join操作
  4. 垂直分区会让事务变得更加复杂

2.2.2 水平分区

  保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分可以支撑非常大的数据量。水平拆分是指数据表行的拆分,表的行数超过300万行时,可以把一张的表的数据拆成多张表来存放。示例:

  • 水平拆分的优点
  1. 可以支持非常大的数据量。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以 水平拆分最好分库 。
  2. 应用端改造也少。
  • 水平拆分的适用场景
  1. 表中的数据本身就有独立性,例如表中分表记录各个地区的数据或者不同时期的数据,特别是有些数据常用,有些不常用。
  2. 需要把数据存放在多个介质上。
  • 水平切分的缺点
  1. 给应用增加复杂度,通常查询时需要多个表名,查询所有数据都需UNION操作。
  2. 在许多数据库应用中,这种复杂度会超过它带来的优点,查询时会增加读一个索引层的磁盘次数。

2.2.3 分库分表后面临的问题

  通常订单的分库分表要么基于订单号Hash取模实现,要么根据用户ID Hash取模实现。订单号Hash取模的好处是数据能均匀分布到各个表中,而缺陷则是一个用户查询所有订单时,需要去多个表中查询。
  由于订单表用户查询比较多,此时我们应该考虑使用用户ID字段做Hash取模,对订单表进行水平分表。如果需要考虑高并发时的订单处理能力,我们可以考虑基于用户ID字段Hash取模实现分库分表。这也是大部分公司对订单表分库分表的处理方式。

  • 1、事务支持(数据的完整性和一致性问题)
      分库分表后,就成了分布式事务。如果依赖数据库本身的分布式事务管理功能去执行事务,将付出高昂的性能代价; 如果由应用程序去协助控制,形成程序逻辑上的事务,又会造成编程方面的负担。
      通常,解决分布式事务有两种通用的方式:两阶事务提交(2PC)以及补偿事务提交(TCC)。
      通常有一些中间件已经帮我们封装好了这两种方式的实现,例如Spring实现的JTA,目前阿里开源的分布式事务中间件Fescar,就很好地实现了与Dubbo的兼容。
  • 2、跨库join
      只要是进行切分,跨节点Join的问题是不可避免的。但是良好的设计和切分却可以减少此类情况的发生。解决这一问题的普遍做法是分两次查询实现:在第一次查询的结果集中找出关联数据的id,根据这些id发起第二次请求得到关联数据
      通常,会冗余表或冗余字段来优化跨库JOIN查询。对于一些基础表,例如商品信息表,我们可以在每一个订单分库中复制一张基础表,避免跨库JOIN查询。而对于一两个字段的查询,我们也可以将少量字段冗余在表中,从而避免JOIN查询,也就避免了跨库JOIN查询。
  • 3、跨节点的count、order by、group by以及聚合函数问题
      这些是一类问题,因为它们都需要基于全部数据集合进行计算。多数的代理都不会自动处理合并工作。解决方案:与解决跨节点join问题的类似,分别在各个节点上得到结果后在应用程序端进行合并。和join不同的是每个结点的查询可以并行执行,因此很多时候它的速度要比单一大表快很多。但如果结果集很大,对应用程序内存的消耗是一个问题。
      通常我们建议使用两套数据来解决跨节点分页查询问题,一套是基于分库分表的用户单条或多条查询数据,一套则是基于Elasticsearch、Solr存储的订单数据,主要用于运营人员根据其它字段进行分页查询。为了不影响提交订单的业务性能,我们一般使用异步消息来实现Elasticsearch、Solr订单数据的新增和修改。
  • 4、数据迁移,容量规划,扩容等问题
      在最开始设计表数据量时,尽量使用 2 的倍数来设置表数量。当我们需要扩容时,也同样按照 2 的倍数来扩容,这种方式可以减少数据的迁移量。
  • 5、全局ID问题
      一旦数据库被切分到多个物理结点上,我们将不能再依赖数据库自身的主键生成机制。一方面,某个分区数据库自生成的ID无法保证在全局上是唯一的;另一方面,应用程序在插入数据之前需要先获得ID,以便进行SQL路由。

  在业务开发之前,首先要根据自己的业务需求来设计表。考虑到一开始的业务发展比较平缓,且开发周期比较短,因此在开发时间比较紧的情况下,尽量不要考虑分表分库。但是我们可以将分表分库的业务接口预留,提前考虑后期分表分库的切分规则,把该冗余的字段提前冗余出来,避免后期分表分库的JOIN查询等。
  当业务发展比较迅速的时候,我们就要评估分表分库的必要性了。一旦需要分表分库,就要结合业务提前规划切分规则,尽量避免消耗性能的跨表跨库JOIN查询、分页查询以及跨库事务等操作。

2.2.4 怎么解决分库分表后带来的问题

  • 1、ACID解决方法
      分布式事务问题可以用两阶段提交协议来解决。在分布式系统中,提交之前增加了准备的阶段,所以称为两阶段提交。事务在第一阶段对资源进行准备,如果在准备阶段有一个资源失败,那么在第二阶段的处理就是回滚所有资源,否则进行Commit操作。
      但在实际应用中,由于事务管理器自身的稳定性、可用性的营销,以及网络通信中可能产生的问题,出现的情况会复杂很多。此外,事务管理器在多个资源之间进行协调,它自身要进行很多日志记录的工作。网络上的交互次数的增多以及引入事务管理器的开销,是使用两阶段提交协议使得分布式事务的开销增大的两个方面。因此,在垂直拆分和水平拆分后,需要想清楚是否一定要引入两阶段的分布式事务,在必要的情况下才建议使用。
  • 2、水平切分ID被破坏
      即多机Sequence问题。当转变为水平分库时,原来但库的Sequence以及ID的做法需要改变。我们需要从连续性和唯一性两个方面来考虑。
      如果从唯一性考虑,我们可以参考UUID的生成方式,或者根据自己的业务情况使用各个种子(不同维度的标识,例如IP、MAC、机器名、时间、本机计数器等因素)来生成唯一的ID。这样生成的ID虽然保证了唯一性,但在整个分布式系统中的连续性不好。
      从ID的连续性考虑,在分布式系统中,可以使用一个独立的系统来完成这个工作。比如把所有ID集中放在一个地方进行管理,对每个ID序列独立管理,每台机器使用ID时都从这个ID生成器上获取。
  • 3、跨库join
      1)将原来的一次从多个表查询分成多次,每次从不同的表查询。
      2)适当的数据冗余。
      3)借助外部系统(例如搜索引擎)解决一些跨库问题。
  • 4、外键约束问题
      不能完全依赖数据库本身来完成之前的功能。如果需要对分库后的单库做外键约束,就需要分库后每个单库的数据是内聚的,否则就只能靠应用层来处理。

2.3 分库分表之后,ID主键如何处理

  因为要是分成多个表之后,每个表都是从1开始累加,这样是不对的,需要一个全局唯一的 id 来支持。
  生成全局id有下面这几种方式:

  • 1、UUID
      不适合作为主键,因为太长了,并且无序不可读,查询效率低。比较适合用于生成唯一的名字的标示比如文件的名字。
  • 2、数据库自增ID
      两台数据库分别设置不同步长,生成不重复ID的策略来实现高可用。这种方式生成的 id 有序,但是需要独立部署数据库实例,成本高,还会有性能瓶颈
  • 3、利用Redis(RedisAtomicLong) 生成ID
      性能比较好,灵活方便,不依赖于数据库。但是,引入了新的组件造成系统更加复杂,存在一定的性能消耗。
  • 4、利用Twitter开源的分布式ID生产算法
      还可以基于Twitter开源的分布式ID生产算法——snowflake解决全局主键ID问题,snowflake是通过分别截取时间、机器标识、顺序计数的位数组成一个long类型的主键ID。这种算法可以满足每秒上万个全局ID生成,不仅性能好,而且低延时。

2.4 优化数据库的方法(概述)

  1. 选取最适用的字段属性,尽可能减少定义字段宽度,尽量把字段设置NOT NULL,例如’省份’、'性别’等固定值,最好使用ENUM。选取合适的存储引擎;
  2. 使用连接(JOIN)来代替子查询,当然尽量避免JOIN查询;
  3. 适用联合(UNION)来代替手动创建的临时表;
  4. 事务处理;
  5. 锁定表、优化事务处理;
  6. 建立索引;
  7. 优化查询语句;
  8. 有外键约束会影响插入和删除性能,如果程序能够保证数据的完整性,那在设计数据库时就去掉外键;
  9. 表中允许适当冗余,譬如,主题帖的回复数量和最后回复时间等;
  10. UNION ALL要比UNION快很多,所以,如果可以确认合并的两个结果集中不包含重复数据且不需要排序时的话,那么就使用UNION ALL。
      UNION和UNION ALL关键字都是将两个结果集合并为一个,但这两者从使用和效率上来说都有所不同:
  1. 对重复结果的处理:UNION在进行表连接后会筛选掉重复的记录,Union All不会去除重复记录
  2. 对排序的处理:Union将会按照字段的顺序进行排序;UNION ALL只是简单的将两个结果合并后就返回
  1. 找规律分表,减少单表中的数据量提高查询速度;
  2. MySQL主从库读写分离

2.5 Mysql数据库的优化(语句和参数)

  说起Mysql优化,要了解一下Mysql原理,这样才能深入的理解那些sql规则。

  MySQL客户端/服务端通信协议是“半双工”的:在任一时刻,要么是服务器向客户端发送数据,要么是客户端向服务器发送数据,这两个动作不能同时发生。一旦一端开始发送消息,另一端要接收完整个消息才能响应它,所以我们无法也无须将一个消息切成小块独立发送,也没有办法进行流量控制。
  客户端用一个单独的数据包将查询请求发送给服务器,所以当查询语句很长的时候,需要设置max_allowed_packet(server接受的数据包的大小)参数。当服务器响应客户端请求时,客户端必须完整的接收整个返回结果,而不能简单的只取前面几条结果,然后让服务器停止发送。因而在实际开发中,尽量保持查询简单且只返回必需的数据,减小通信间数据包的大小和数量是一个非常好的习惯,这也是查询中尽量避免使用SELECT *以及加上LIMIT限制的原因之一

2.5.1 sql的优化

  通过explain和show profiles进行执行计划分析,找出问题,进行针对性的优化。其中创建高效索引是最有效的一个手段。

  • 1、多列索引和索引顺序
      出现多个索引做相交操作时(多个AND条件),通常来说一个包含所有相关列的索引要优于多个独立索引。
      在选择性高的字段上建立索引,可以让MySQL在查询时过滤掉更多的行。对于多列索引,哪个索引字段在前面,取决于索引的选择性的高低。选择性高的索引排在前面,有利于提高查询效率。例如联合索引(user_group_id,trade_amount)用户的群组肯定比订单的交易金额的选择性高。
  • 2、覆盖索引
      如果一个索引包含或者说覆盖所有需要查询的字段的值,那么就没有必要再回表查询,这就称为覆盖索引。覆盖索引是非常有用的工具,可以极大的提高性能,因为查询只需要扫描索引会带来许多好处:

  优化关联查询:以小表驱动大表。
  子查询尽量换成join。这是因为join,MySQL不需要在内存中创建临时表来完成这个逻辑上的需求。
  确保任何的GROUP BY和ORDER BY中的表达式只涉及到一个表中的列,这样MySQL才有可能使用索引来优化。

  • 3、优化LIMIT分页
      一个常见的问题是当偏移量非常大的时候,比如:LIMIT 10000 20这样的查询,MySQL需要查询10020条记录然后只返回20条记录,前面的10000条都将被抛弃,这样的代价非常高。优化这种查询一个最简单的办法就是尽可能的使用覆盖索引扫描,而不是查询所有的列,然后根据需要做一次关联查询再返回所有的列。对于偏移量很大时,这样做的效率会提升非常大。
      考虑下面的查询,修改前:
 SELECT film_id,description FROM film ORDER BY title LIMIT 50,5;

  修改后:

 SELECT film.film_id,film.descriptionFROM film INNER JOIN (SELECT film_id FROM film ORDER BY title LIMIT 50,5) AS tmp USING(film_id);
  • 4、优化UNION
      除非确实需要服务器去重,否则就一定要使用UNION ALL,如果没有ALL关键字,MySQL会给临时表加上DISTINCT选项,这会导致整个临时表的数据做唯一性检查,这样做的代价非常高。
  • 5、避免导致索引失效
  1. 负向条件查询不能使用索引(not in/not exists都不是好习惯)
  2. 前导模糊查询不能使用索引(like’‘XX%’')
  3. 数据区分度不大的字段不宜使用索引
  4. 在属性上进行计算不能命中索引
  5. 复合索引最左前缀不满足
  • 6、强制类型转换会全表扫描

2.5.2 合理设置mysql的部分参数

  • 1、thread_pool_size
      同时运行sql语句的mysql的线程数。如果主引擎为InnoDB,thread_pool_size最佳设置可能在16和36之间,最常见的优化值倾向于24到36。
  • 2、thread_pool_stall_limit
      超时时间,线程在超过 thread_pool_size 时,会等待 thread_pool_stall_limit ms 后创建新线程,防止线程池瞬间扩展而还来不必要的线程开销。用处理被阻塞和长时间运行的语句,确保服务器不完全被阻塞。设置过长会导致线程被阻塞,引起性能问题。
  • 3、tmp_table_size
      通过设置tmp_table_size选项来增加一张临时表的大小,例如做order by ,GROUP BY操作生成的临时表。如果调高该值,MySQL同时将增加heap表的大小,可达到提高联接查询速度的效果,建议尽量优化查询,要确保查询过程中生成的临时表在内存中,避免临时表过大导致生成基于硬盘的MyISAM表。

2.6 Mysql性能优化(架构)

  说到性能调优,大部分时候想要实现的目标是让我们的查询更快。一个查询的动作又是由很多个环节组成的,每个环节都会消耗时间,要减少查询所消耗的时间,就要从每一个环节入手。

2.6.1 连接——配置优化

  第一个环节是客户端连接到服务端,在这个环节,可能服务端连接数不够导致应用程序获取不到连接。

  • 1、从服务端来说,可以增加服务端的可用连接数
      如果有多个应用或者很多请求同时访问数据库,连接数不够的时候,可以调两个参数:max_connections和wait_timeout。
      1)修改配置参数增加可用连接数,修改max_connections的大小:
 -- 修改最大连接数show variables like 'max_connections';

    2)或者及时释放不活动的连接。交互式和非交互式的客户端的默认超时时间都是28800秒,8小时,我们可以把这个值调小:

 --及时释放不活动的连接, 注意不要释放连接池还在使用的连接show global variables like 'wait_timeout';
  • 2、从客户端来说,可以减少从服务端获取的连接数
      即引入连接池,实现连接的重用。
      可以在哪些层面使用连接池?ORM层面(MyBatis 自带了一个连接池);或者使用专用的连接池工具(阿里的Druid、Spring Boot 2.x版本默认的连接池Hikari、老牌的DBCP和C3P0)。
      连接池并不是越大越好,只要维护一定数量大小的连接池,其他的客户端排队等待获取连接就可以了。有的时候连接池越大,效率反而越低。如:Druid的默认最大连接池大小是8。Hikari的默认最大连接池大小是10。
      在Hikari的github文档中,给出了一个PostgreSQL数据库建议的设置连接池大小的公式:它的建议是机器核数乘以2加1。也就是说,4核的机器,连接池维护9个连接就够了。这个公式从一定程度上来说对其他数据库也是适用的。

2.6.2 缓存——架构优化

  在应用系统的并发数非常大的情况下,如果没有缓存,会造成两个问题:一方面是会给数据库带来很大的压力。另一方面,从应用的层面来说,操作数据的速度也会受到影响。可以用第三方的缓存服务来解决这个问题,例如Redis。

  • 1、主从复制
      如果单台数据库服务满足不了访问需求,那我们可以做数据库的集群方案。这个时候需要用到复制技术(replication),被复制的节点称为master,复制的节点称为slave。slave本身也可以作为其他节点的数据来源,这个叫做级联复制。
      做了主从复制的方案之后,我们只把数据写入master节点,而读的请求可以分担到slave节点。我们把这种方案叫做读写分离。
      读写分离可以一定程度低减轻数据库服务器的访问压力,但是需要注意主从数据一致性的问题。
  1. 单线程
      在早期的MySQL中,slave的SQL线程是单线程。master可以支持SQL语句的并行执行,配置了多少的最大连接数就是最多同时多少个SQL并行执行。而slave的SQL却只能单线程排队执行,在主库并发量很大的情况下,同步数据肯定会出现延迟。
  2. 异步与全同步
      在主从复制的过程中,MySQL默认是异步复制的。也就是说,对于主节点来说,写入binlog,事务结束,就返回给客户端了。对于slave来说,接收到binlog,就完事儿了,master不关心slave的数据有没有写入成功。
      等待全部从库的事务执行完毕,才返回给客户端,这样的方式叫做全同步复制。从库写完数据,主库才返会给客户端。但这种方式事务执行的时间会变长,它会导致 master 节点性能下降。
  3. 半同步复制
      半同步复制:主库在执行完客户端提交的事务后不是立刻返回给客户端,而是等待至少一个从库接收到binlog并写到relay log中才返回给客户端。master不会等待很长的时间,但是返回给客户端的时候,数据就即将写入成功了,因为它只剩最后一步了:就是读取relay log,写入从库。
      半同步复制提高了数据的安全性,同时它也造成了一定程度的延迟,它需要等待一个slave写入中继日志,这里多了一个网络交互的过程,所以,半同步复制最好在低延时的网络中使用。

这个是从主库和从库连接的角度,来保证slave数据的写入。

  1. 多库并行复制
      MySQL 5.6版本里面支持了多库并行复制,即在不同的数据库执行不同的语句:

      在大部分的情况下,我们都是单库多表的情况,在一个数据库里面怎么实现并行复制呢?很多时候他们本身就是互相不干扰的,比如这些事务是操作不同的表,或者操作不同的行,不存在资源的竞争和数据的干扰。
  2. 异步复制之GTID复制
      可以把那些在主库上并行执行的事务,分为一个组,并且给他们编号,这一个组的事务在从库上面也可以并行执行。这个编号,我们把它叫做GTID,这种主从复制的方式,我们把它叫做基于GTID的复制。
      
      要使用GTID复制,我们可以通过修改配置参数打开它,默认是关闭的,查看命令:
show global variables like 'gtid_mode';

  无论是优化master和slave的连接方式,还是让从库可以并行执行SQL,都是从数据库的层面去解决主从复制延迟的问题。除了数据库本身的层面之外,在应用层面,我们也有一些减少主从同步延迟的方法。
  如果单个master节点或者单张表存储的数据过大的时候,单表的查询性能还是会下降,我们要进一步对单台数据库节点的数据分型拆分,这个就是分库分表。

  • 2、分库分表
      垂直分库,减少并发压力。水平分表,解决存储瓶颈。
      垂直分库的做法,把一个数据库按照业务拆分成不同的数据库。
      水平分库分表的做法,把单张表的数据按照一定的规则分布到多个数据库。
      通过主从或者分库分表可以减少单个数据库节点的访问压力和存储压力,达到提升数据库性能的目的,但是如果 master 节点挂了,怎么办?此时就要用集群方案了。

2.7 分表分库

  • 什么时候要分表分库
      在单表的情况下,当业务正常时,我们使用单表即可,而当业务出现了性能瓶颈时,我们首先考虑用分区的方式来优化,如果分区优化之后仍然存在后遗症,此时我们再来考虑分表分库。
      如果在单表单库的情况下,当数据库表的数据量逐渐累积到一定的数量时(5000W行或100G以上),操作数据库的性能会出现明显下降,即使我们使用索引优化或读写库分离,性能依然存在瓶颈。此时,如果每日数据增长量非常大,我们就应该考虑分表,避免单表数据量过大,造成数据库操作性能下降。
  • 如何分表分库
      分表分库分为垂直切分和水平切分两种。

2.7.1 垂直分表

  垂直分库是指根据业务来分库,不同的业务使用不同的数据库。
  垂直分表则是指根据一张表中的字段,将一张表划分为两张表,其规则就是将一些不经常使用的字段拆分到另一张表中。
  垂直划分数据库是根据业务进行划分,例如购物场景,可以将库中涉及商品、订单、用户的表分别划分出成一个库,通过降低单库的大小来提高性能。同样的,分表的情况就是将一个大表根据业务功能拆分成一个个子表,例如商品基本信息和商品描述,商品基本信息一般会展示在商品列表,商品描述在商品详情页,可以将商品基本信息和商品描述拆分成两张表。

  优点:行记录变小,数据页可以存放更多记录,在查询时减少I/O次数。
  缺点:

  1. 主键出现冗余,需要管理冗余列;
  2. 会引起表连接JOIN操作,可以通过在业务服务器上进行JOIN来减少数据库压力;
  3. 依然存在单表数据量过大的问题。

2.7.2 水平拆分

  水平划分是根据一定规则,例如时间或id序列值等进行数据的拆分。比如根据年份来拆分不同的数据库。每个数据库结构一致,但是数据得以拆分,从而提升性能。

  水平分表只是在一个库中,如果存在连接数、I/O读写以及网络吞吐等瓶颈,就需要考虑将水平切换的表分布到不同机器的库中,这就是水平分库分表。
  结合以上垂直切分和水平切分,一般可以将数据库分为:单库单表 - 单库多表 - 多库多表。在平时的业务开发中,我们应该优先考虑单库单表;如果数据量比较大,且热点数据比较集中、历史数据很少访问,我们可以考虑表分区;如果访问热点数据分散,基本上所有的数据都会访问到,我们可以考虑单库多表;如果并发量比较高、海量数据以及每日新增数据量巨大,我们可以考虑多库多表。
  优点:单库(表)的数据量得以减少,提高性能;切分出的表结构相同,程序改动较少。
  缺点:

  1. 分片事务一致性难以解决;
  2. 跨节点join性能差,逻辑复杂;
  3. 数据分片在扩容时需要迁移。

2.8 数据库优化的相关问题

2.8.1 MySQL数据库cpu飙升到500%的话怎么处理

  当cpu飙升到500%时,先用操作系统命令 top 命令(实时显示系统中各个进程的资源占用状况)观察是不是mysqld占用导致的,如果不是,找出占用高的进程,并进行相关处理。
  如果是mysqld造成的, show processlist(显示用户正在运行的线程),看看里面跑的session情况,是不是有消耗资源的sql在运行。找出消耗高的sql,看看执行计划是否准确, index是否缺失,或者实在是数据量太大造成。
  一般来说,肯定要kill掉这些线程(同时观察cpu使用率是否下降),等进行相应的调整(比如说加索引、改sql、改内存参数)之后,再重新跑这些SQL
  也有可能是每个 sql消耗资源并不多,但是突然之间,有大量的session连进来导致cpu飙升,这种情况就需要跟应用一起来分析为何连接数会激增,再做出相应的调整,比如说限制连接数等。

2.8.2 表中有大字段X(例如:text 类型),且字段X不会经常更新,以读为主,将该字段拆成子表好处是什么

  如果字段里面有大字段(text,blob)类型的,而且这些字段的访问并不多,这时候放在一起就变成缺点了。 MYSQL数据库的记录存储是按行存储的,数据块大小又是固定的(16K),每条记录越小,相同的块存储的记录就越多。此时应该把大字段拆走,这样应付大部分小字段的查询时,就能提高效率。
  当需要查询大字段时,此时的关联查询是不可避免的,但也是值得的。拆分开后,对字段的UPDAE就要UPDATE多个表了。

2.8.3 一些数据库设计的注意事项

  1. 表名一般以【模块名称_具体表名】来实现,同一个模块的前缀是一样的。(Oracle大小写敏感,在SQL中可以不用"_",因为可以用大小写一起的写法。这也是可以的)
  2. 表名称不应该取得太长(一般不超过三个英文单词,不推荐使用中文拼音,总的长度不要超过30个字符)。
  3. 不使用tab或tb作为表前缀
  4. 一些作为多对多连接的表,可以使用两个表的前缀作为表名:如:用户登录表User_Login,用户分组表User_GroupInfo,这两个表建立多对多关系的表名为:User_Group_Relation(关系统一用Relation)。注意一点,主键在做其他表的外键时,或者在被其他表引用时,字段说明和字段名尽量保持一致,比如发帖表BBS_Topic里的用户字段写成UI_ID,这样跟用户信息表User_Info的主键UI_ID保持一致,看起来舒服,对应关系很明确,也不容易错,前后不一致时容易令人费解。
  5. 当系统中有一些少量的,重复出现的值时,使用字典表来节约存储空间和优化查询。如地区、系统中用户类型的代号等。这类值不会在程序的运行期变化,但是需要存储在数据库中。一般数据库中,都有一个数据字典表,用来保存系统所用到的基础数据,大型的字段表如省份城市区域的字典表,统一以Dictionary_作为前缀。
  6. 默认的一些特殊字段,很多表中,比如一些业务处理表中,除了添加生成的自动编号ID(一般作为主键用),如:

  该记录创建的时间CreateDate(创建时间),该记录的创建人CreatBy,最后修改人LastEditBy,最后修改时间LastEditDate。(这些可以直接使用中文字符,而不使用编码,提高查询的效率)
  同时有的时候需要注意,删除的时候并不真的删除该记录,而是添加一个标识位,比如XX_DeleteStaus删除状态。1是有效的,0则是无效的。

  1. 数据库中应建立这样一个表,就是数据库本身的字段信息,表的说明,也就是数据库设计文档的一个表,方便查询使用,有什么不明的可以直接从数据库查询,数据库文档丢失,注释丢失,都可以重新起作用。
  2. 每个表都应该有一个主键,这个主键最好是数字,而且是递增的,有很多表的主键用32位字符编码,这样做的目的更多的是从安全考虑的。因为字符多时索引时效率低,而使用自增列也不是很少,比如添加主表和从表操作时,主表的主键是从表的外键,这个时候还有取返回值,然后再添加,不可以同时添加。主键可以用自定义的规则,大部分用MAX(ID)的做法,也可以自定义一个序列表,有点像序列,或者用时间的年月日秒具体到毫秒。关于列的命名,建议对数据类型也做一些规范,因为很容易确定,只有四种主要类型:数字,字符,时间,逻辑值,这些在类型上和长度上都可以定好规范,统一起来。
  3. 基本表及其字段之间的关系,应尽量满足第三范式。但是,满足第三范式的数据库设计,往往不是最好的设计。为了提高数据库的运行效率,常常需要降低范式标准:适当增加冗余,达到以空间换时间的目的
  4. 若两个实体之间存在多对多的关系,则应消除这种关系。消除的办法是,在两者之间增加第三个实体。这样,原来一个多对多的关系,现在变为两个一对多的关系。要将原来两个实体的属性合理地分配到三个实体中去。这里的第三个实体,实质上是一个较复杂的关系,它对应一张基本表。
  5. 主键PK的取值方法,PK是供程序员使用的表间连接工具,可以是一无物理意义的数字串,由程序自动加1来实现。也可以是有物理意义的字段名或字段名的组合。不过前者比后者好。当PK是字段名的组合时,建议字段的个数不要太多,多了不但索引占用空间大,而且速度也慢。
  6. 中间表是存放统计数据的表,它是为数据仓库、输出报表或查询结果而设计的,有时它没有主键与外键(数据仓库除外)。临时表是程序员个人设计的,存放临时记录,为个人所用。基表和中间表由DBA维护,临时表由程序员自己用程序自动维护。
  7. 字段允许适当冗余
      字段允许适当冗余,以提高查询性能,但必须考虑数据一致。冗余字段应遵循:

  1) 不是频繁修改的字段。
  2) 不是唯一索引的字段。
  3) 不是varchar超长字段,更不能是text字段。

三、Mysql日志

3.1 Redo日志和Undo日志

  这两种日志是存储引擎层面的日志

  • 1、Redo日志
      Redo日志,是Innodb存储引擎的日志文件。Redolog用来记录innodb存储引擎的事务日志,不管事务是否提交都会记录下来,用于数据恢复。当数据库发生故障,innoDB 存储引擎会使用Redolog恢复到发生故障前的时刻,以此来保证数据的完整性。将参数innodb_flush_log_at_tx_commit设置为1,那么在执行commit时会将redo log同步写到磁盘。
      当发生数据修改的时候,innodb引擎会先将记录写到redo log中,并更新内存,此时更新就算是完成了,同时innodb引擎会在合适的时机将记录操作到磁盘中。
       Redolog是固定大小的,是循环写的过程。
      有了redolog之后,innodb就可以保证即使数据库发生异常重启,之前的记录也不会丢失,叫做crash-safe。
      Redo保证了持久性(隔离性是通过锁来实现)。
  • 2、Undo日志
      Undo Log是为了实现事务的原子性,在MySQL数据库InnoDB存储引擎中,还用Undo Log来实现多版本并发控制。
      在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为Undo Log)。然后进行数据的修改。如果出现了错误或者用户执行了ROLLBACK语句,系统可以利用Undo Log中的备份将数据恢复到事务开始之前的状态。
      除了记录Redolog外,当进行数据修改时还会记录undo log, undo log用于数据的撤回操作,它保留了记录修改前的内容。通过undo log可以实现事务回滚,并且可以根据undo log回溯到某个特定的版本的数据, 实现MVCC。

  注意:undo log是逻辑日志,可以理解为:

当delete一条记录时,undo log中会记录一条对应的insert记录;
当insert一条记录时,undo log中会记录一条对应的delete记录;
当update一条记录时,它记录一条对应相反的update记录。

3.2 Binlog日志

   Binlog是Server层面的日志,主要做mysql功能层面的事情。
  Binlog与redo日志的区别:

  1. redo是innodb独有的,binlog是所有引擎都可以使用的
  2. redo是物理日志,记录的是在某个数据页上做了什么修改,binlog是逻辑日志,记录的是这个语句的原始逻辑
  3. redo是循环写的,空间会用完,binlog是可以追加写的,不会覆盖之前的日志信息

  Binlog中会记录所有的逻辑,并且采用追加写的方式。一般在企业中数据库会有备份系统(用于应付数据丢失等情况),可以定期执行备份,备份的周期可以自己设置。
  Binlog主要用于恢复数据库和同步数据库。
 恢复数据的过程:

  1. 找到最近一次的全量备份数据
  2. 从备份的时间点开始,将备份的binlog取出来,重放到要恢复的那个时刻

3.3 数据更新的流程


  执行流程:

  1. 执行器先从引擎中找到数据,如果在内存中直接返回,如果不在内存中,查询后返回;
  2. 执行器拿到数据之后会先修改数据,然后调用引擎接口重新写入数据;
  3. 引擎将数据更新到内存,同时写数据到redo中,此时处于prepare阶段,并通知执行器执行完成,随时可以操作;
  4. 执行器生成这个操作的Binlog;
  5. 执行器调用引擎的事务提交接口,引擎把刚刚写完的redo改成commit状态,更新完成。

  Redo log为什么两阶段提交?

  • 1、先写redo log后写binlog
      假设在redo log写完,binlog还没有写完的时候,MySQL进程异常重启。由于我们前面说过的,redo log写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行c的值是1。但是由于binlog没写完就crash了,这时候binlog里面就没有记录这个语句。因此,之后备份日志的时候,存起来的binlog里面就没有这条语句。然后你会发现,如果需要用这个binlog来恢复临时库的话,由于这个语句的binlog丢失,这个临时库就会少了这一次更新,恢复出来的这一行c的值就是0,与原库的值不同。
  • 2、先写binlog后写redo log
      如果在binlog写完之后crash,由于redo log还没写,崩溃恢复以后这个事务无效,所以这一行c的值是 0。但是binlog里面已经记录了“把c从0改成1”这个日志。所以,在之后用binlog来恢复的时候就多了一个事务出来,恢复出来的这一行c的值就是1,与原库的值不同。

  可以看到,如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。

3.4 binlog和redolog的区别

  • binlog会记录所有日志记录,包括InnoDB、MyISAM等存储引擎的日志;redolog只记录innoDB自身的事务日志。
  • binlog只在事务提交前写入到磁盘,一个事务只写一次;而在事务进行过程,会有redolog不断写入磁盘。
  • binlog是逻辑日志,记录的是SQL语句的原始逻辑;redolog是物理日志,记录的是在某个数据页上做了什么修改。

3.5 MySQL记录binlog的方式主要包括三种模式?每种模式的优缺点是什么?

  Mysql复制主要有三种方式:基于SQL语句的复制、基于行的复制、混合模式复制。对应的,binlog的格式也有三种:STATEMENT,ROW,MIXED。
  1)STATEMENT模式:每一条会修改数据的sql语句会记录到binlog中。优点是并不需要记录每一条sql语句和每一行的数据变化,减少了binlog日志量,节约IO,提高性能。缺点是在某些情况下会导致master-slave中的数据不一致( 如sleep()函数, last_insert_id(),以及user-defined functions(udf)等会出现问题)。
  2)ROW模式:不记录每条sql语句的上下文信息,仅需记录哪条数据被修改了,修改成什么样了。而且不会出 现某些特定情况下的存储过程、或function、或trigger的调用和触发无法被正确复制的问题。缺点是会产生大量的日志,尤其是alter table的时候会让日志暴涨。
  3)MIXED模式:以上两种模式的混合使用,一般的复制使用STATEMENT模式保存binlog,对于STATEMENT模式无法复制的操作使用ROW模式保存binlog,MySQL会根据执行的SQL语句选择日志保存方式。

四、主从复制

  • 为什么需要主从复制?
  1. 在业务复杂的系统中,有这么一个情景,有一句sql语句需要锁表,导致暂时不能使用读的服务,那么就很影响运行中的业务,使用主从复制,让主库负责写,从库负责读,这样,即使主库出现了锁表的情景,通过读从库也可以保证业务的正常运作
  2. 做数据的热备。
  3. 架构的扩展。业务量越来越大,I/O访问频率过高,单机无法满足,此时做多库的存储,降低磁盘I/O访问的频率,提高单个机器的I/O性能。

  MySQL主从复制是指数据可以从一个MySQL数据库服务器主节点复制到一个或多个从节点。
  MySQL 默认采用异步复制方式,这样从节点不用一直访问主服务器来更新自己的数据,数据的更新可以在远程连接上进行,从节点可以复制主数据库中的所有数据库或者特定的数据库,或者特定的表。

4.1 Mysql的复制原理以及流程

  主从复制:将主数据库中的DDL(数据定义语言,CREATE,DROP,ALTER:主要为创建、修改、删除数据库的逻辑结构,其中包括表结构,视图和索引等)和DML(数据操纵语言,INSERT,UPDATE,DELETE:主要用于数据库中数据的修改,包括添加、删除、修改等)操作通过二进制日志(Binlog)传输到从数据库上,然后将这些日志重新执行(重做);从而使得从数据库的数据与主数据库保持一致。

  • 1、主从复制的作用

主数据库出现问题,可以切换到从数据库。
可以进行数据库层面的读写分离。
可以在从数据库上进行日常备份。

  • 2、MySQL主从复制解决的问题

数据分布:随意开始或停止复制,并在不同地理位置分布数据备份。
负载均衡:降低单个服务器的压力。
高可用和故障切换:帮助应用程序避免单点失败。
升级测试:可以用更高版本的MySQL作为从库。

  • 3、MySQL主从复制工作原理

在主库上把数据更高记录到二进制日志。
从库将主库的日志复制到自己的中继日志。
从库读取中继日志的事件,将其重放到从库数据中。


  在主从复制时,涉及到3个线程:
  Binlog线程:主服务器上的线程,该县城记录下所有改变了数据库数据的语句,放进主服务器上的Binlog中;
  IO线程:从服务器上的线程,在使用start slave命令之后,负责从主服务器上拉取Binlog内容,放进从服务器上的relay log中;
  Sql执行线程:从服务器上的线程,执行relay log中的语句。
  上面三个线程涉及了2个线程:

  • Binary log:主数据库的二进制日志;
  • Relay log:从服务器的中继日志。

  主从复制流程:

  • 1、master服务器将数据的改变记录二进制binlog日志,当master上的数据发生改变时,则将其改变写入二进制日志中;
  • 2、slave服务器会在一定时间间隔内对master二进制日志进行探测其是否发生改变,如果发生改变,则开始一个I/OThread请求master二进制事件;
  • 3、同时主节点为每个I/O线程启动一个dump线程,用于向其发送二进制事件,并保存至从节点本地的中继日志(relay log)中,从节点将启动SQL线程从中继日志中读取二进制日志,在本地重放,使得其数据和主节点的保持一致,最后I/OThread和SQLThread将进入睡眠状态,等待下一次被唤醒

4.2 Mysql支持的复制类型

  这个问题也可以换个说法:MySQL的binlog的格式。Binlog是记录所有数据库表结构变更(例如CREATE、ALTER TABLE…)以及表数据修改(INSERT、UPDATE、DELETE…)的二进制日志。Binlog是Server层的日志。
  MySQL的Binlog有三种格式:statment、row和mixed。

4.2.1 基于语句的复制(statment)

  每一条会修改数据的sql都会记录在binlog中
  在主服务器上执行的 SQL 语句,在从服务器上执行同样的语句。Mysql默认采用基于语句的复制,效率比较高。 一旦发现没法精确复制时,会自动选着基于行的复制。

  • 优点
      不需要记录每一行的变化,减少了Binlog日志量,节约了IO,提高性能。(相比row能节约多少性能与日志量,这个取决于应用的SQL情况,正常同一条记录修改或者插入row格式所产生的日志量还小于Statement产生的日志量,但是考虑到如果带条件的update操作,以及整表删除,alter表等操作,ROW格式会产生大量日志,因此在考虑是否使用ROW格式日志时应该跟据应用的实际情况,其所产生的日志量会增加多少,以及带来的IO性能问题。)
  • 缺点
      由于记录的只是执行语句,为了这些语句能在slave上正确运行,因此还必须记录每条语句在执行的时候的一些相关信息,以保证所有语句能在slave得到和在master端执行时候相同的结果。

4.2.2 基于行的复制(row)

  不记录sql语句上下文相关信息,仅保存哪条记录被修改
  把改变的内容复制过去,而不是把命令在从服务器上执行一遍. 从Mysql5.0 开始支持。

  • 优点
      : binlog中可以不记录执行的sql语句的上下文相关的信息,仅需要记录那一条记录被修改成什么了。所以rowlevel的日志内容会非常清楚的记录下每一行数据修改的细节。而且不会出现某些特定情况下的存储过程,或function,以及trigger的调用和触发无法被正确复制的问题。
  • 缺点
      所有的执行的语句当记录到日志中的时候,都将以每行记录的修改来记录,这样可能会产生大量的日志内容,比如一条update语句,修改多条记录,则binlog中每一条修改都会有记录,这样造成binlog日志量会很大,特别是当执行alter table之类的语句的时候,由于表结构修改,每条记录都发生改变,那么该表每一条记录都会记录到日志中。

  新版的Mysql中对row级别也做了一些优化,当表结构发生变化的时候,会记录语句而不是逐行记录。

4.2.3 混合类型的复制(mixed)

   是以上两种方式的混合使用,一般的语句修改使用statment格式保存binlog,如一些函数,statement无法完成主从复制的操作,则采用row格式保存binlog
  默认采用基于语句的复制,一旦发现基于语句的无法精确的复制时,就会采用基于行的复制。

4.2.4 Binlog基本配置与格式设定

  • 1、基本配置
      Mysql BInlog日志格式可以通过mysql的my.cnf文件的属性binlog_format指定。示例:
binlog_format        = MIXED                 //binlog日志格式
log_bin              = 目录/mysql-bin.log    //binlog日志名
expire_logs_days     = 7                    //binlog过期清理时间
max_binlog_size      = 100m                 //binlog每个日志文件大小
  • 2、Binlog日志格式选择
      Mysql默认是使用Statement日志格式,推荐使用mixed
      由于一些特殊使用,可以考虑使用row,如自己通过Binlog日志来同步数据的修改,这样会节省很多相关操作。对于Binlog数据处理会变得非常轻松,相对mixed,解析也会很轻松(当然前提是增加的日志量所带来的IO开销在容忍的范围内即可)。

4.3 主从复制的几个问题

  • 1、master的写操作,slaves被动的进行一样的操作,保持数据一致性,那么slave是否可以主动的进行写操作?
      假设slave可以主动的进行写操作,slave又无法通知master,这样就导致了master和slave数据不一致了。因此slave不应该进行写操作,至少是slave上涉及到复制的数据库不可以写
  • 2、从复制中,可以有N个slave,可是这些slave又不能进行写操作,他们的作用是什么?
      主要用于实现分担负载,可以将读的任务分散到slaves上。数据备份,从而实现高可用的功能,一旦master挂了,可以让slave顶上去,同时slave提升为master
  • 3、 当主服务器的二进制日志每产生一个事件,都需要发往从服务器,如果有N个从服务器, 那是发N次,还是只发一次?
      应该发 N 次。实际上, 在MySQL的master内部 , 维护N个线程 , 每一个线程负责将二进制日志文件发往对应的slave。master既要负责写操作,还的维护N个线程,负担会很重。
      可以这样:slave-1是master的从服务器,slave-1又是slave-2、slave-3,…的主服务器。slave-1将master的复制线程的负担,转移到自己的身上。 这就是所谓的多级复制的概念。

4.4 Mysql主从形式

  有以下五种形式:一主一从、主主复制、一主多从、多主一从、联级复制。




4.5 Mysql主从同步延时分析

  mysql的主从复制都是单线程的操作,主库对所有DDL和DML产生的日志写进binlog,由于binlog是顺序写,所以效率很高,slave的sql thread线程将主库的DDL和DML操作事件在slave中重放。DML和DDL的IO操作是随机的,不是顺序,所以成本要高很多,另一方面,由于sql thread也是单线程的,当主库的并发较高时,产生的DML数量超过slave的SQL thread所能处理的速度,或者当slave中有大型query语句产生了锁等待,那么延时就产生了。
  解决方案:

  1. 业务的持久化层的实现采用分库架构,Mysql服务可平行扩展,分散压力。
  2. 单个库读写分离,一主多从,主写从读,分散压力。这样从库压力比主库高,保护主库。
  3. 服务的基础架构在业务和Mysql之间加入memcache或者redis的cache层。降低mysql的读压力。
  4. 不同业务的Mysql物理上放在不同机器,分散压力。
  5. 使用比主库更好的硬件设备作为slave,Mysql压力小,延迟自然会变小。

4.6 主从复制示例

  两个虚拟机:ip:192.168.2.21(主) ip:192.168.2.22(从)。

4.6.1 配置主库

  • 1、准备创建一个新用户用于从库连接主库
# 创建用户
create user 'repl'@'%' identified by 'repl'; # 授权,只授予复制和客户端访问权限
grant replication slave,replication client on *.* to 'repl'@'%' identified by 'repl';
  • 2、修改配置文件
      /etc/my.cnf 在[mysqld]下添加:
log-bin         = mysql-bin
log-bin-index   = mysql-bin.index
binlog_format   = mixed
server-id       = 21
sync-binlog     = 1
character-set-server = utf8

  保存文件并重启主库:

service mysqld restart

  配置说明:

log-bin:设置二进制日志文件的基本名;
log-bin-index:设置二进制日志索引文件名;
binlog_format:控制二进制日志格式,进而控制了复制类型,三个可选值:
 -STATEMENT:语句复制
 -ROW:行复制
 -MIXED:混和复制,默认选项
server-id:服务器设置唯一ID,默认为1,推荐取IP最后部分;
sync-binlog:默认为0,为保证不会丢失数据,需设置为1,用于强制每次提交事务时,同步二进制日志到磁盘上。

  • 3、备份主数据库数据
      若主库可以停机,则直接拷贝所有数据库文件。若主库是在线生产库,可采用mysqldump备份数据,因为它对所有存储引擎均可使用。
      为了获取一个一致性的快照,需对所有表设置读锁:
flush tables with read lock;

  获取二进制日志的坐标:

show master status;

  返回结果示例:

+------------------+----------+--------------+------------------+-------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 120 |              | |                   |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)

  备份数据:

# 针对事务性引擎
mysqldump -uroot -ptiger --all-database -e --single-transaction --flush-logs --max_allowed_packet=1048576 --net_buffer_length=16384 > /data/all_db.sql # 针对 MyISAM 引擎,或多引擎混合的数据库
mysqldump -uroot --all-database -e -l --flush-logs --max_allowed_packet=1048576 --net_buffer_length=16384 > /data/all_db.sql

  恢复主库的写操作:

unlock tables;

4.6.2 配置从库

  • 1、修改配置文件
      /etc/my.cnf 在[mysqld]下添加:
log-bin             = mysql-bin
binlog_format       = mixed
log-slave-updates   = 0
server-id           = 22
relay-log           = mysql-relay-bin
relay-log-index     = mysql-relay-bin.index
read-only           = 1
slave_net_timeout   = 10

  保存文件并重启从库:

service mysqld restart

  配置说明:

  1. log-slave-updates:控制 slave 上的更新是否写入二进制日志,默认为0;若 slave 只作为从服务器,则不必启用;若 slave 作为其他服务器的 master,则需启用,启用时需和 log-bin、binlog-format 一起使用,这样 slave 从主库读取日志并重做,然后记录到自己的二进制日志中;
  2. relay-log:设置中继日志文件基本名;
  3. relay-log-index:设置中继日志索引文件名;
  4. read-only:设置 slave 为只读,但具有super权限的用户仍然可写;
  5. slave_net_timeout:设置网络超时时间,即多长时间测试一下主从是否连接,默认为3600秒,即1小时,这个值在生产环境过大,我们将其修改为10秒,即若主从中断10秒,则触发重新连接动作。
  • 2、导入备份数据
mysql -uroot -p < /data/all_db.sql
  • 3、统一二进制日志的坐标
      此处使用的是新创建的账户:
change master to
master_host='192.168.2.21',
master_user='repl',
master_password='repl',
master_port=3306,
master_log_file='mysql-bin.000001',
master_log_pos=120;
  • 4、启动主从复制
      启动从库slave线程:
start slave;

  查看从服务器复制功能状态:

show slave status

五、数据库优化的相关问题

5.1 读写分离有哪些解决方案

  读写分离是依赖于主从复制,而主从复制又是为读写分离服务的,因为主从复制要求slave不能写只能读。

  • 1、使用mysql-proxy代理
      优点:直接实现读写分离和负载均衡,不用修改代码,master和slave用一样的帐号,mysql官方不建议实际生产中使用。
      缺点:降低性能, 不支持事务。
  • 2、使用AbstractRoutingDataSource+aop+annotation
      在dao层决定数据源。如果采用了mybatis, 可以将读写分离放在ORM层,比如mybatis可以通过mybatis plugin拦截sql语句,所有的insert/update/delete都访问master库,所有的select 都访问salve库,这样对于dao层都是透明。 plugin实现时可以通过注解或者分析语句是读写方法来选定主从库。不过这样依然有一个问题, 也就是不支持事务, 所以我们还需要重写一下DataSourceTransactionManager, 将read-only的事务扔进读库, 其余的有读有写的扔进写库。
  • 3、使用AbstractRoutingDataSource+aop+annotation
      在service层决定数据源,可以支持事务. 缺点:类内部方法通过this.xx()方式相互调用时,aop不会进行拦截,需进行特殊处理。

Mysql(五)Mysql架构、数据库优化、主从复制相关推荐

  1. mppdb 查看建表语句_MPP架构数据库优化总结——华为LibrA与GreenPlum

    文章目录 MPP架构数据库优化总结--华为LibrA与GreenPlum 1. 简介 2. 优化点 2.1 建表时选择合适的数据类型,可以提高效率.减小空间占用 2.2 选择合理的存储模型(行存和列存 ...

  2. MPP架构数据库优化总结——华为LibrA(MPPDB、GuassDB)

    文章目录 MPP架构数据库优化总结--华为LibrA(MPPDB.GuassDB) 1. 简介 2. 优化点 2.1 建表时选择合适的数据类型 2.2 选择合理的存储模型(行存和列存) 2.3 选择表 ...

  3. MySQL(五)MySQL事务

    事务是数据库管理系统(DBMS)执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成.                                                       ...

  4. MySQL亿级数据数据库优化方案测试-银行交易流水记录的查询

    对MySQL的性能和亿级数据的处理方法思考,以及分库分表到底该如何做,在什么场景比较合适? 比如银行交易流水记录的查询 限盐少许,上实际实验过程,以下是在实验的过程中做一些操作,以及踩过的一些坑,我觉 ...

  5. mysql schedule every_Mysql 架构及优化之-定时计划任务

    概论 mysql计划任务可以定时更新数据库表或者做大文件的汇总表 配置 开启计划任务 SHOW VARIABLES LIKE 'event_scheduler' 查看是否开启 off 表示未开启 se ...

  6. mysql aa复制_MySQL的复制架构与优化

    MySQL的复制架构与优化 ###########原理########### 1.主服务器将更新的数据的sql语句(例如,insert,update,delete等)写入到 二进制文件中(由log-b ...

  7. 运维角度浅谈MySQL数据库优化(转自:2018-03-10 李振良 JAVA高级架构)

    一个成熟的数据库架构并不是一开始设计就具备高可用.高伸缩等特性的,它是随着用户量的增加,基础架构才逐渐完善.这篇博文主要谈MySQL数据库发展周期中所面临的问题及优化方案,暂且抛开前端应用不说,大致分 ...

  8. 架构-浅谈MySQL数据库优化

    主从复制博文:http://lizhenliang.blog.51cto.com/7876557/1290431 读写分离博文:http://lizhenliang.blog.51cto.com/78 ...

  9. 视频教程-怎么架构生产数据库--生产数据库优化的一种方式-MySQL

    怎么架构生产数据库--生产数据库优化的一种方式 运维经理,高级架构师.曾任职于NEC软件.海尔B2B平台巨商汇,负责企业数据平台构建.B2B电商平台数据管理与搭建.企业运维管理平台搭建.拥有丰富DBA ...

最新文章

  1. php 工资 2018,2018年我国公务员级别工资标准
  2. 怎么用linux设计一个小程序,“Linux”小程序发布一个月后,我们发现了什么
  3. mysql设置了utf8mb4还是报错_第07期:有关 MySQL 字符集的 SQL 语句
  4. DW的代码格式化和净化功能
  5. 飞猪基于 Serverless 的云+端实践与思考
  6. 使用@host获得宿主元素注入器里注入的内容
  7. jsp用tags传递参数
  8. 精通 WPF UI Virtualization
  9. C++函数的分文件编写
  10. Chapter 6. H.264/MPEG4 Part10
  11. 宿舍电源额定500w,我的电脑550w的,有什么办法能解决吗?
  12. 转行数据分析,是选Python还是R?
  13. 造轮子,常用JS处理HTML工具(HTMLUtils)
  14. 用 theano 求解 Logistic Regression (SGD 优化算法)
  15. RabbitMQ中Confirm确认与Return返回消息详解(八)
  16. 速腾(RoboSense)16线激光雷达调试出点云图(Ubuntu1804和windows系统都已经显示点云),包含各种遇见的坑【避坑指南】{[driver][socket]Rslidar poll}
  17. Salient Object Detection Driven by Fixation Prediction 论文解读
  18. 玩转“数独”2.0时代
  19. linux 免费教程下载,Linux系统入门教程
  20. 动态规划的理解(DP)

热门文章

  1. Google App Engine的app.yaml详细说明
  2. 手机wps系统打印服务器,MK-WPS101w单USB无线打印服务器网页版教程
  3. 最全ASCII码对照表0-255(包括扩展)
  4. comp9021 第三课
  5. java根据url获取文件流
  6. Xposed Installer框架 安卓版
  7. 腾讯云网站备案授权码使用教程
  8. 用 telnet 访问网站发送 HTTP 请求
  9. webgame 转(一篇炮哄非处女的狂文)<3>
  10. FTDI可实现USB转并口的芯片