MySQL性能优化十个实用技巧

目录

一、 表结构设计

1.1 MySQL数据库的页大小

1.2 B树和B+树的区别

1.3 表结构设计

二、 建索引

三、 SQL优化

3.1 SQL编写

3.2 常用的SQL优化方法

3.2.1 业务层逻辑优化

3.2.2 SQL性能优化

3.2.3 索引优化

3.3 慢SQL优化

3.3.1 什么是慢查询

3.3.2 慢查询的影响

3.3.3 如何解决慢查询

3.3.4 对于中小型企业和大厂的不同关注点

3.3.5 总结

四、 数据分区

4.1 表分区

4.2 分区表

4.3 水平分区

📝 1. 范围模式

📝 2. 哈希模式

📝 3. 列表模式

📝 4. 复合模式

五、 灾备处理

5.1 冷备份

5.2 热备份

5.3 冷备份与热备份的权衡

5.4 备份注意事项

六、 高可用

6.1 MMM

6.2 MHA

6.3 MGR

七、 异常发现处理

7.1 数据库监控

📝 数据库监控的功能介绍

🔥 1. 实时监控

🔥 2. 异常报警

🔥 3. 性能优化

🔥 4. 安全管理

🔥 5. 统计数据与分析

📝 目前主流的数据库监控工具介绍

🔥 1. Prometheus + Grafana + mysqld_exporter

🔥 2. SolarWinds SQL Sentry

🔥 3. Database Performance Analyzer

🔥 4. OpenFalcon

7.2 数据库日志

📝 1. 重做日志

📝 2. 回滚日志

📝 3. 变更日志/二进制日志

📝 4. 慢查询日志

📝 5. 错误日志

📝 6. 通用查询日志

📝 7. 中继日志

📝 8. 数据定义语句日志

📝 9. processlist日志

📝 10. innodb status日志

7.3 数据库巡检

7.4 资源评估

八、 数据服务

8.1 子表结构生成

8.2 数据迁移

8.3 数据校验

📝 前端传入参数的数据校验

📝 程序插入数据库中的数据的校验

📝 数据迁移的源数据库和目标数据库的表数据的对比

8.4 总结

九、 读写分离

9.1 主从数据同步

📝 优化业务读写访问

🔥 1. 读写分离

🔥 2. 批量提交

📝 增加从库数量

📝 采用异步同步方式

9.2 中间件路由

9.3 缓存路由

十、 主从复制

10.1 主从复制(replication)的工作原理

📝 主从复制的基本概念

📝 主服务器和从服务器

📝 二进制日志和中继日志

🔥 1、二进制日志

🔥 2、中继日志

📝 主从复制的具体实现

🔥 1、主服务器记录二进制日志

🔥 2、从服务器从主服务器上复制二进制日志

🔥 3、从服务器重做中继日志中的日志

📝 主从复制的优缺点

🔥 1、主从复制的优点

🔥 2、主从复制的缺点

10.2 主从复制bin log 日志有几种记录方式,说说各自的优缺点

10.3 主从复制有几种方式?

📝 异步复制

📝 半同步复制

📝 增强半同步复制

🔥普通半同步的问题

🔥增强半同步的解决方法


一、 表结构设计

在数据库设计中,表结构设计是非常重要的一部分。在这篇文章中,我们将会讨论MySQL数据库的页大小、B树和B+树的区别,以及开发者在设计表时应该注意哪些点以提高查询速度和存储空间利用率。

1.1 MySQL数据库的页大小

MySQL数据库将数据从磁盘读取到内存,使用磁盘块作为基本单位进行读取。如果一个数据块中的数据一次性被读取,那么查询效率将会提高。

以InnoDB存储引擎为例,它使用页作为数据读取单位。页是磁盘管理的最小单位,默认大小为16KB。由于系统的磁盘块存储空间通常没有这么大,InnoDB在申请磁盘空间时会使用多个地址连续的磁盘块来达到页的大小16KB。

查询数据时,一个页中的每条数据都能帮助定位到数据记录的位置,从而减少磁盘I/O操作,提高查询效率。InnoDB存储引擎在设计时会将根节点常驻内存,尽力使树的深度不超过3。这意味着在查询过程中,I/O操作不超过3次。树形结构的数据可以让系统高效地找到数据所在的磁盘块。

1.2 B树和B+树的区别

在这里讨论一下B树和B+树的区别。B树的结构是每个节点既包含key值也包含value值,而每个页的存储空间是16KB。如果数据较大,将会导致一个页能存储数据量的数量很小。相比之下,B+树的结构是将所有数据记录节点按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息。这样可以大大加大每个节点存储的key值数量,降低B+树的高度。

1.3 表结构设计

通过了解MySQL数据库底层存储的原理和数据结构,开发者在设计表时应该尽量减少单行数据的大小,将字段宽度设置得尽可能小。以下是一些可以注意的点,以提高查询速度和存储空间利用率:

  1. 避免使用text、Blob、Clob等大数据类型,它们占用的存储空间更大,读取速度较慢。如果需要存储大数据,可以选择使用外部存储服务存储。
  2. 尽量使用数字型字段,如性别字段用0/1的方式表示,而不是男女。这样可以控制数据量,增加同一高度下B+树容纳的数据量,提高检索速度。
  3. 使用varchar/nvarchar代替char/nchar。变长字段存储空间较小,可以节省存储空间。
  4. 不在数据库中存储图片、文件等大数据,可以通过第三方云存储服务存储,并提供图片或文件地址。
  5. 金额字段使用decimal类型,注意长度和精度。如果存储的数据范围超过decimal的范围,建议将数据拆成整数和小数分开存储。
  6. 避免给数据库留null值。尤其是时间、整数等类型,可以在建表时就设置非空约束。NULL列会使用更多的存储空间,在MySQL中处理NULL值也更复杂。为NULL的列可能导致固定大小的索引变成可变大小的索引,例如只有整数列的索引。

以上是表结构设计方面的一些注意点,希望对开发者们在MySQL数据库的设计中有所帮助。

二、 建索引

索引是一种可以提高查询性能的方法,但是如果不明确建立索引对于哪些字段,可能会影响到数据的维护速度。下面给大家介绍一些建立索引时需要权衡的情况。

情况一:经常修改的数据不适合建立索引

如果一个字段需要经常被修改,那么建立索引会影响到数据的维护速度,因此不适合对这些字段建立索引。比如说,对于一个状态字段,可能需要经常修改,那么建立索引就不是一个好的选择。

情况二:考虑字段的区分度是否高

区分度越高的字段,建立索引的效果越好。例如,如果一个性别字段的区分度很高,比如说90%的用户是男性,10%是女性,那么建立索引会提高查询性能。但是如果区分度不高,比如说90%的用户是男性,10%是女性,那么建立索引并不能提高查询性能。

情况三:可以在where及order by涉及的列上建立索引

查询时经常会用到where和order by,所以可以在这些列上建立索引,以提高查询性能。比如说,如果有一个表需要经常根据日期排序,那么可以在日期字段上建立索引。

情况四:需要查询排序、分组和联合操作的字段,适合建立索引

如果需要对某些字段进行排序、分组和联合操作,那么可以在这些字段上建立索引,以提高查询性能。比如说,如果需要对某个表的数据进行分组,可以在分组字段上建立索引。

情况五:索引并非越多越好,最好不要超过6个

虽然索引可以提高查询性能,但是索引并非越多越好。一个表的索引数最好不要超过6个。当为多个字段创建索引时,表的更新速度会减慢,因此应选择具有较高区分度且不经常更改的字段创建索引。

情况六:尽量让字段顺序与索引顺序一致

如果一个表有多个字段需要建立索引,那么最好让字段顺序与索引顺序一致。比如说,如果需要为一个表的a、b、c三个字段建立索引,并且查询时通常会用到a和b两个字段,那么最好的顺序是a和b先建立索引,然后是c。

情况七:最左前缀原则

最左前缀原则是指,尽量确保查询中的索引列按照最左侧的列进行匹配。比如说,如果需要查询一个表的a、b、c三个字段,而a和b两个字段已经建立了索引,那么最好的查询顺序是先查询a和b,再查询c。这样可以保证索引的效果最好。

总的来说,建立索引需要权衡数据的维护速度和查询性能,需要考虑字段的区分度、是否经常修改、查询的顺序等因素。只有在合适的情况下建立索引,才能提高查询性能。

三、 SQL优化

为了优化SQL语句,需要了解数据库的架构、索引、查询优化器以及各种SQL执行引擎的机制等技术知识。

3.1 SQL编写

在编写SQL语句时,开发者需要注意一些关键点以提高查询性能。以下是一些建议:

(1)避免在WHERE子句中对查询的列执行范围查询(如NULL值判断、!=、<>、or作为连接条件、IN、NOT IN、LIKE模糊查询、BETWEEN)和使用“=”操作符左侧进行函数操作、算术运算或表达式运算,因为这可能导致索引失效,从而导致全表扫描。

(2)对于JOIN操作,如果数据量较大,先分页再JOIN可以避免大量逻辑读,从而提高性能。

(3)使用COUNT()可能导致全表扫描,如有WHERE条件的SQL,WHERE条件字段未创建索引会进行全表扫描。COUNT()只统计总行数,聚簇索引的叶子节点存储整行记录,非聚簇索引的叶子节点存储行记录主键值。非聚簇索引比聚簇索引小,选择最小的非聚簇索引扫表更高效。

(4)当数据量较大时,查询只返回必要的列和行,LIMIT 分页限制返回的数据,减少请求的数据量,插入建议分批次批量插入,以提高性能。

(5)对于大连接的查询SQL,由于数据量较多、又是多表,容易出现整个事务日志较大,消耗大量资源,从而导致一些小查询阻塞,所以优化方向是将它拆分成单表查询,在应用程序中关联结果,这样更利于高性能可伸缩,同时由于是单表减少了锁竞争效率上也有一定提升。

(6)尽量明确只查询所需列,避免使用SELECT *。SELECT *会导致全表扫描,降低性能。若必须使用SELECT *,可以考虑使用MySQL 5.6及以上版本,因为这些版本提供了离散读优化(Discretized Read Optimization),将离散度高的列放在联合索引的前面,以提高性能。

索引下推(ICP,Index Condition Pushdown)优化:ICP优化将部分WHERE条件的过滤操作下推到存储引擎层,减少上层SQL层对记录的索取,从而提高性能。在某些查询场景下,ICP优化可以大大减少上层SQL层与存储引擎的交互,提高查询速度。

多范围读取(MRR,Multi-Range Read)优化:MRR优化将磁盘随机访问转化为顺序访问,提高查询性能。当查询辅助索引时,首先根据结果将查询得到的索引键值存放于缓存中。然后,根据主键对缓存中的数据进行排序,并按照排序顺序进行书签查找。

这种顺序查找减少了对缓冲池中页的离散加载次数,可以提高批量处理对键值查询操作的性能。

在编写SQL时,使用EXPLAIN语句观察索引是否失效是个好习惯。索引失效的原因有以下几点:

(1)如果查询条件中包含OR,即使其中部分条件带有索引,也无法使用。

(2)对于复合索引,如果不使用前列,后续列也无法使用。

(3)如果查询条件中的列类型是字符串,则在条件中将数据使用引号引用起来非常重要,否则索引可能失效。

(4)如果在查询条件中使用运算符(如+、-、*、/等)或函数(如substring、concat等),索引将无法使用。

(5)如果MySQL认为全表扫描比使用索引更快,则可能不使用索引。在数据较少的情况下尤其如此。

3.2 常用的SQL优化方法

常用的SQL优化方法,包括业务层逻辑优化、SQL性能优化和索引优化。这些方法都可以有效地提高数据库的查询效率,从而让我们的应用程序更加快速和流畅。

3.2.1 业务层逻辑优化

业务层逻辑优化是SQL优化的第一步。在进行SQL优化之前,我们需要仔细地审核我们的业务逻辑,因为良好的业务逻辑能够减轻数据库的访问压力。举个例子,假设我们有一个电子商务网站,用户可以在该网站上浏览商品并购买。如果用户的浏览量非常大,而且每个页面都需要从数据库中获取数据,则会给数据库带来很大的负担。解决这个问题的方法是将大的业务逻辑拆分成小的逻辑块并行处理。举个例子,我们可以通过使用缓存来减少数据库的访问次数,这样可以提高查询效率并减轻数据库的负担。

3.2.2 SQL性能优化

SQL性能优化是SQL优化的核心部分。优化SQL能够减少数据库的访问次数,从而提高查询效率。优化SQL可以从几个方面入手,如编写优化的SQL语句、创建合适的索引、使用缓存和批量操作等。

🔥 1. 编写优化的SQL语句

在编写SQL语句时,我们需要考虑查询的效率。我们可以通过使用内连接、外连接或子查询等方式来优化SQL语句。例如,我们可以通过使用INNER JOIN代替WHERE子句来改进查询效率。当然,还有一些其他的优化SQL语句的技巧,如使用反向查询、优化分页查询等等。总之,我们需要时刻关注查询效率,并尽可能地编写优化的SQL语句。

🔥 2. 创建合适的索引

创建索引是提高查询效率的常用方法。索引是使用一种特殊的数据结构来加速数据库的数据访问。通常,我们可以通过创建索引来加速查询操作。例如,我们可以在常用的查询字段上创建索引。这样可以加快查询速度,并减少数据库的访问次数。但是,如果我们创建太多的索引,反而会降低查询效率,并使数据库变得缓慢。

🔥 3. 使用缓存和批量操作

使用缓存和批量操作可以减少数据库的访问次数,从而提高查询效率。例如,我们可以使用缓存来缓存常用的查询数据。当下一次查询相同的数据时,我们可以直接从缓存中获取,而不必再次查询数据库。此外,我们还可以使用批量操作来减少数据库的访问次数。例如,我们可以使用INSERT INTO … VALUES语句来插入多个数据,这样可以减少多次INSERT操作的次数。

3.2.3 索引优化

索引优化是SQL优化的重要方法。索引是一种特殊的数据结构,可以加速数据库的访问速度。索引优化可以减少数据库查询的响应时间,提高查询效率。在对数据表创建索引时,我们需要考虑一些因素,如索引的类型、字段选择、索引的数量等等。此外,我们还需要使用索引优化工具,将优化过程工具化、标准化。最好是在提供SQL语句的同时,给出索引优化建议。

3.3 慢SQL优化

你是否曾经遇到过打开网页或操作软件时变得异常缓慢的情况?你是否曾经为了查找一个数据而在数据库中听到了“咯吱咯吱”的声音?这些问题可能都源自于我们经常忽略的慢查询。慢查询是指在执行时所需的时间超出了特定阈值的SQL查询,即使你不知道,它依然会对你的系统造成潜在的威胁。

3.3.1 什么是慢查询

慢查询是指在执行时,需要的时间超过了特定阈值的SQL查询。这个时间阈值通常是根据你的应用程序性能要求和硬件规格来决定的。对于一个小型应用而言,慢查询可能是需要1秒完成的查询,而对于大型应用来说,慢查询的阈值可能会更高。

慢查询通常是由于查询语句的执行计划不佳或表中的数据量过大而引起的。当慢查询发生时,需要尽快发现问题并解决它,以避免对应用程序的性能产生不利影响。

3.3.2 慢查询的影响

慢查询对于数据库的影响可能是灾难性的。它会导致数据库负载过高,降低整个系统的性能。如果你的应用程序中涉及到慢查询,那么可能会出现以下几个问题:

  1. 网站或应用程序的响应时间变慢,用户体验变差。
  2. 当大量用户同时访问网站或应用程序时,服务器可能会崩溃或挂起。
  3. 在运行时,程序可能会“卡住”,无法响应请求。
  4. 数据库管理人员可能无法识别具体的慢查询SQL,导致故障的处理时间变长。
3.3.3 如何解决慢查询

🔥 1. 定期监控慢查询

监控慢查询可以及早发现问题,减少对应用程序和数据库的影响。一些工具,例如Percona Toolkit和pt-query-digest等,可以帮助你分析日志和监控数据库。你可以使用这些工具来列出最慢的查询,并检查需要优化的查询的数量和性质。这些工具还能帮助你识别慢查询的原因,例如查询执行的方式是否正确,是否存在不必要的全表扫描等。

🔥 2. 优化查询

优化查询是一个非常重要的过程。如果你的查询语句无法执行,或者需要太长时间才能完成,那么你需要对查询进行优化。

例如,你可以通过更好地构造查询语句来减少数据的访问量。同时,你还可以通过索引优化查询来提高查询的性能。如果你发现查询中存在一些不必要的操作,例如使用JOIN查询时,你也可以优化查询的方式,以减少查询时间。

🔥 3. 提高硬件性能

如果你的应用程序中涉及到的查询量很大,那么你可能需要考虑提高硬件的性能。你可以增加RAM、CPU或更大的硬盘空间,以提高查询的响应时间。同时,你还可以考虑升级数据库软件的版本,以获得更好的性能。

🔥 4. 使用缓存

缓存是另一个优化查询的方法。当你的查询结果不会频繁变化时,你可以将这些结果缓存起来,以提高查询的响应时间。例如,在一个电子商务网站中,商品的价格只会在特定时间内变化。当查询价格时,你可以将结果缓存起来,以避免每次查询时都要重新计算价格。

🔥 5. 使用分片技术

如果你的应用程序中涉及到大量的数据,那么你可能需要使用分片技术。分片是将一个大型数据库拆分成多个更小的数据库的过程。这种技术可以帮助你提高查询的性能,并减少数据库的负载。例如,当用户在一个电子商务网站上搜索商品时,你可以将所有商品的数据分为几个不同的数据库。这样,当用户执行搜索时,每个数据库只需要处理一部分数据,从而提高查询的响应时间。

3.3.4 对于中小型企业和大厂的不同关注点

在中小型企业,由于项目进度等原因,慢查询通常会被忽略。然而,在大型企业中,慢查询是一个非常重要的问题。在这些大型企业中,数据库管理员需要通过实时分析慢查询日志来对比历史慢查询,并给出优化建议。这些管理员通常会花费大量的时间和精力来监控数据库,并及时调整,降低故障处理时长。

在大型企业中,影响程度较大的慢查询通常会导致数据库负载过高,需要进行人工故障诊断。这时,管理员需要快速识别具体的慢查询SQL,并及时调整,以避免故障处理时间变长。当前未被定义为慢查询的SQL可能会随着时间的推移变成慢查询,甚至对核心业务造成影响。因此,在大型企业中,需要对这些潜在的慢查询进行分类接入:

(1) 未上线准慢查询:未上线准慢查询通常需要通过发布前集成测试流水线来解决。在这个过程中,通常会使用经验和explain关键字来识别慢查询。只有在解决缺陷后才能发布上线。

(2) 已上线准慢查询:已上线准慢查询通常是由于表数据量的增加而导致的。在这种情况下,通常会变成全表扫描。开发者可以增加慢查询配置参数log_queries_not_using_indexes,将查询记录到慢日志中,并实时跟进治理。

3.3.5 总结

慢查询可能会对应用程序、数据库和用户造成负面影响。因此,我们需要及时发现和解决慢查询问题,以保持应用程序的性能。在解决慢查询问题时,我们可以使用一些工具和技术,例如定期监控慢查询、优化查询、提高硬件性能、使用缓存和使用分片技术等。在大型企业中,慢查询通常由数据库管理员通过实时分析慢查询日志来处理。我们需要对不同类型的慢查询进行分类和治理,以避免对核心业务产生影响。

四、 数据分区

在面对大量数据时,分区可以帮助提高查询性能。分区主要分为两类:表分区和分区表。

4.1 表分区

表分区,顾名思义,就是把表按照一定的规则划分为多个分区,每个分区独立存储在磁盘上。通过这种方式,可以让查询操作更加高效,同时也可以提高数据的可靠性和可用性。表分区是在创建表时定义的,需要在表建立的时候创建规则。如果要修改已有的有规则的表分区,只能新增,不能随意删除。

那么,为什么要使用表分区呢?首先,表分区可以提高查询的速度。当数据量较大时,查询整个表的时间会非常长,而使用表分区可以只查询需要的分区,这样就能大大缩短查询的时间。此外,表分区能够提高数据的可靠性和可用性。在一些情况下,如果一份数据损坏或丢失,可能会导致整个表无法使用。而表分区可以将数据划分为多个分区,这样即使有一个分区损坏或丢失,也不会对整个表造成影响。

接下来,我们来看一下表分区的局限性。在MySQL中,每个表最多只能有1024个分区,这是一个比较大的限制。此外,在已有的有规则的表分区中,只能新增分区,不能随意删除。这意味着如果我们需要删除某个分区,就必须重新创建整个表,然后将数据重新导入。这样会消耗大量的时间和精力,因此需要在设计表分区时慎重考虑。

那么,关于表分区的创建规则有哪些呢?在MySQL中,表分区可以根据不同的规则进行划分,常见的规则有以下几种:

  1. 按照范围分区(RANGE)
    按照给定的范围对表进行划分,例如根据时间范围进行划分。
  2. 按照列表分区(LIST)
    根据列中的枚举值对表进行划分。
  3. 按照哈希分区(HASH)
    根据哈希值对表进行划分。
  4. 按照键值分区(KEY)
    根据列值的哈希值对表进行划分。

那么,我们来看一个具体的例子。假设我们有一个记录公司员工信息的表,其中包括员工编号、姓名、部门、工资等信息。我们希望根据员工所在的部门对表进行分区,具体的划分规则如下:

  • 部门编号为1的员工存储在分区1中
  • 部门编号为2的员工存储在分区2中
  • 部门编号为3的员工存储在分区3中

接下来,我们可以使用以下命令来创建这个表分区:

-- 创建名为 employee 的表,包含 id、name、department、salary 四个字段
CREATE TABLE employee (
  id INT,                           -- 员工 ID,整数类型
  name VARCHAR(20),                 -- 员工姓名,字符串类型,最大长度为 20
  department INT,                   -- 员工所属部门,整数类型
  salary INT                        -- 员工薪资,整数类型
)
-- 按照 department 字段进行分区
PARTITION BY RANGE (department) (
  PARTITION p1 VALUES LESS THAN (2), -- 第一个分区,department 值小于 2 的数据存储在此分区中
  PARTITION p2 VALUES LESS THAN (3), -- 第二个分区,department 值小于 3 的数据存储在此分区中
  PARTITION p3 VALUES LESS THAN (4)  -- 第三个分区,department 值小于 4 的数据存储在此分区中
);

通过这个命令,我们就成功创建了一个按照部门进行分区的表。当我们需要查询某个部门的员工信息时,只需要查询对应的分区即可,大大提升了查询效率。

总之,表分区是一个非常有用的技术,可以提高查询效率,同时也可以提高数据的可靠性和可用性。当然,我们也需要注意表分区的局限性,慎重考虑分区的规则和数量。希望本篇文章对大家有所启发,如果还有什么问题或者想法,欢迎在评论区留言。

4.2 分区表

在我们日常生活中,许多数据都需要进行存储和处理,如各类用户信息、订单数据、商品信息等等。而这些数据都需要被保存在数据库中,以便于后续的查询和分析。但是,在数据量变得越来越大的情况下,表分区的限制也逐渐变得凸显。

当单表的数据量达到一定程度时,就需要考虑进行表分区了。表分区是指将一个表按照某种规则拆分成多个分区,每个分区包含相应的数据。这样做的好处是可以提高查询和分析的效率。

但是,当表分区达到上限时,我们可以考虑采用垂直拆分和水平拆分的方法。

垂直拆分将单表变为多表,以增加每个分区承载的数据量。例如,我们有一张包含用户信息的表,可以将其拆分为基本信息表和扩展信息表两张表。这样,基本信息表可以存储用户名、性别、年龄等基本信息,而扩展信息表则可以存储更多的用户信息,如电话号码、邮箱地址等等。这样拆分后,每个表的数据量就会变得更小,同时查询效率也会更高。

但是,垂直拆分也有一些缺点。首先,我们需要考虑数据的关联性,即将哪些字段放到哪个表中。如果拆分不当,反而会导致查询效率降低。其次,在进行SQL查询时,我们需要反复测试以确保性能。因为此时我们需要多个表之间的JOIN操作,会带来额外的开销。

另一种拆分方式是水平拆分,即将数据按照某种策略拆分为多个表。例如,我们可以按照时间或者地域等因素拆分数据。如果我们有一张订单表,可以根据下单时间将其拆分为多个表,如2019年订单表、2020年订单表等等。这样拆分后,每个表的数据量也会变得更小,查询效率也会更高。

与垂直拆分相比,水平拆分的优点在于可以更加灵活地进行数据处理。但是,它也有着一些缺点。首先,需要考虑数据的局部性,即不同的数据分区之间的关系。其次,进行SQL查询时,我们需要使用UNION操作将不同分区的数据合并。这些操作都会带来额外的开销和复杂度。

对于包含大文本和BLOB列的表,如果这些列不经常被访问,可以将它们划分到另一个分区,以保证数据相关性的同时提高查询速度。因为大文本和BLOB数据占用的空间较大,同时处理起来也比较复杂,如果将其与其他数据放在同一个分区中,会影响查询效率。

总之,表分区是提高数据库查询效率的一种重要手段。当表分区达到上限时,我们可以考虑采用垂直拆分和水平拆分的方式,以提高查询效率。同时,需要根据具体情况选择合适的拆分方式,并进行反复测试以确保性能。

4.3 水平分区

水平分区指将表中的数据划分为多个部分,每个部分都存储在不同的物理位置上,以便于进行管理和查询。在本文中,我们将详细介绍水平分区的四种模式。

📝 1. 范围模式

在范围模式下,数据库管理员(DBA)可以根据表中列的具体值范围来划分数据。例如,将一个表按照时间划分为三个部分,分别是80年代的数据、90年代的数据以及2000年以后的数据。这种方法通常使用范围值来进行划分,比如时间、价格、年龄等。这种模式适用于数据按照一定的时间或数量规律进行划分的情况。

举个例子,假设你是一个物流公司的DBA,你需要将订单表分成不同的区域以便于管理和查询。你可以按照地理位置对订单表进行范围分区,如华北地区、华东地区、华南地区、西北地区、西南地区等。这样,你就能够根据不同的地理位置来查询订单数据,而不需要扫描整张表。

📝 2. 哈希模式

在哈希模式下,DBA可以通过对表的一个或多个列进行哈希计算,将数据划分为多个部分。哈希计算会将列中的值映射到一个哈希码上,并根据哈希码的不同值将数据分配到不同的分区。这种分区方法通常使用主键或唯一索引列作为哈希码。

例如,假设你是一个在线游戏公司的DBA,你需要在数据库中存储海量的玩家数据。为了避免单一表过大,你可以根据玩家ID将玩家数据进行哈希分区。这样,即使玩家数据极其庞大,你仍然可以快速地查询和管理它们。

📝 3. 列表模式

在列表模式下,DBA可以将表中的数据按照列中的一组特定值进行划分,每个特定值对应一个数据分区。这种分区方法通常用于列中的值具有固定的集合,比如月份、季度、地区、部门等。

例如,假设你是一家电商公司的DBA,你需要管理客户订单数据。为了便于查询和管理,你可以将订单表按照订单日期分割为三个不同的分区,分别对应2021年、2022年和2023年的订单数据。这样,你就可以更轻松地查询和分析不同年份的订单数据。

📝 4. 复合模式

在复合模式下,DBA可以将多个分区模式组合使用,以实现更复杂的数据分区需求。例如,可以在已经进行范围分区的表上,对其中一个分区再进行哈希分区。

例如,假设你是一家银行的DBA,你可以将账户表按照账户类型和开户日期进行复合分区。这样,你就可以更好地管理不同类型的账户,并且根据需要查询不同开户日期范围内的账户数据。

总之,水平分区是数据库设计中的一种重要技术,可以提高数据库的性能和可扩展性。不同的分区模式有不同的应用场景,DBA可以根据具体情况选择合适的模式来进行分区。

五、 灾备处理

在MySQL中,冷热备份可以帮助 开发者在不影响性能的情况下确保数据的安全性。

5.1 冷备份

故事开始在一家小公司的IT部门,主角小明是这个部门的一名工程师。他负责维护公司的数据库,保障数据的安全和可靠性。可是他总是感觉自己的备份策略不够完善,备份速度太慢,备份的数据量太大,并且备份的安全性也存在一定的风险。为了解决这个问题,他开始研究起了“冷备份”。

首先,小明需要弄清楚什么是冷备份。简单来说,冷备份是在数据不再被频繁访问或使用时,将数据进行备份的一种备份策略。该策略可以在数据库关闭时进行,备份数据的速度更快,同时安全性也更高。这与“热备份”的概念相反,热备份是在数据库运行时进行的备份,需要在繁忙的业务时段进行,会影响数据库的性能和可用性。

小明听了这些解释,还是有些模糊。为了更好地理解冷备份,小明找来了一个例子。他告诉我们,在他的公司里,每个月都会有一份月度报告生成。但是这份报告的数据量较大,且仅在其生成之后的短时间内使用频繁,之后便不再被使用。这样的数据就很适合进行冷备份。小明可以将这些数据备份到外部存储设备中,以确保在需要时可以轻松访问这些数据。这样,小明就可以释放数据库的空间,提高数据库的性能和可用性。

既然知道了什么是冷备份,小明又开始考虑如何实现。他意识到,实现冷备份需要遵循以下步骤:

首先,需要确定备份的数据对象。即哪些数据需要进行冷备份。一般应该选择那些占用大量存储空间,但访问频率较低的数据进行备份。

其次,需要选择备份的方法。冷备份的方式有很多种,比如硬盘备份、磁带备份等,小明需要根据自己的实际情况选择适合自己的备份方式。

然后,需要选择备份的时间。冷备份一般在数据库关闭时进行,这样可以避免备份时对数据库的影响。小明建议在非工作时间进行备份,以避免对业务的影响。

最后,需要存储备份数据。备份数据需要存储在外部设备中,以防止备份数据也遭到损坏。另外,备份数据的存储位置应该是安全的,可以考虑使用加密技术等手段进行保护。

小明在实践中发现,冷备份还有一些优点:

首先,备份速度快。因为在关闭数据库后进行备份,减少了备份时数据的访问量,因此备份速度更快。

其次,数据安全性高。因为备份数据存储在外部设备中,不受数据库故障等因素的影响。

最后,备份成本低。冷备份一般使用较为成熟、可靠的备份技术,可以有效控制备份成本。

小明在实践中遇到了一些问题。例如,如何保证备份数据的一致性?如何保证备份的及时性?如何保证备份数据的可用性?针对这些问题,小明做了以下总结:

首先,要保证备份前的一致性。如果数据备份时,数据处于不一致状态可能会产生数据问题。因此,在进行冷备份的时候,应该确保所有相关的数据均已提交到磁盘,以确保备份前是一致的状态。

其次,要保证备份的及时性。冷备份虽然不需要对数据库进行正常备份,但也要保证备份的及时性。如果备份时间太晚,容易丢失关键的数据。

最后,要保证备份数据的可用性。备份的目的是为了防止数据丢失,如果备份数据本身丢失或不可用就无法实现备份的目的了。因此,在进行冷备份时,应该确保备份数据本身是可靠的,存储位置是安全的。

经过几个月的策划和试验,小明成功地实现了冷备份,并且取得了很好的效果。不仅减少了数据备份的数据量,加快了备份的速度,而且数据的安全性得到了有效保障。小明对冷备份的研究也得到了同事们的赞赏和认可。

5.2 热备份

在我们的日常生活中,我们经常处理大量的数据以及信息。这些信息可能包括我们的个人信息、工作上的记录、交易历史、甚至还有我们的社交媒体账号。对于这些信息,我们通常都需要对它们进行备份,以防发生不可预测的意外情况,如电脑崩溃、硬盘故障、黑客攻击等等。

备份数据的目的是为了让我们能够在需要的时候快速恢复数据,尤其是在数据丢失的情况下,这样才能够避免损失。而在备份数据时,热备份无疑是备份数据的一种非常不错的方式。

那么,什么是热备份呢?

简单来说,热备份是在应用程序运行时进行的数据备份。它可以对数据库中的数据进行备份,备份的是SQL操作语句等数据,这些数据可以是我们的个人信息、购物记录、在线账号、会员资料等。而备份的数据可以存储在云端等在线存储服务中,以便在需要时可以方便地查看这些数据。

与传统的备份方式相比,热备份有很多优势。首先,热备份可以在应用程序运行时进行,而不会影响到应用程序的正常运行。其次,热备份的备份频率可以非常高,可以每秒钟备份一次,确保备份数据的实时性。最后,热备份可以自动化,减少人工操作的风险。

让我们来看一个具体的例子。假设你是一位电商平台的管理员,你需要管理平台上的大量用户数据。这些数据包括用户的购物记录、个人信息、账户余额等。如果这些数据突然丢失,那将是一场灾难。因此,你需要进行热备份。

你可以选择将电商平台的数据库中的SQL操作语句备份到云端的在线存储服务中。这样,只要用户进行任何操作,如购物、修改个人信息等,都会被备份下来。如果数据库出现任何故障,你可以通过备份数据来恢复数据库,保证数据不会丢失。

除了备份电商平台的用户数据,热备份还可以应用于很多其他场景。例如,你可以将你的个人电脑或手机上的数据备份到云端。这样,即使你的电脑或手机出现故障,你的数据也能够轻松地恢复。

总之,热备份是一种非常高效、可靠的数据备份方式,可以在很多场景下使用。通过热备份,我们可以保护我们的数据不被丢失,以及在需要时快速地恢复数据。

5.3 冷备份与热备份的权衡

(1)冷备份速度更快,因为它不涉及应用程序的运行,但可能需要外部存储设备。

(2)热备份速度较慢,因为它涉及应用程序的运行和数据库操作的记录。

(3)冷备份更安全,因为它在数据库关闭时进行,不受应用程序影响。

(4)热备份安全性稍低,因为它在应用程序运行时进行,需要保持设备和网络环境的稳定性。

5.4 备份注意事项

(1)备份过程中要保持设备和网络环境稳定,避免因中断导致数据丢失。

(2)备份时需要仔细小心,确保备份数据的正确性,以防止恢复过程中出现问题。

(3)热备份操作要特别仔细,备份SQL操作语句时不能出错。

总之,通过对冷热数据进行备份,可以在不影响应用程序性能的情况下确保数据的安全性。在实际应用中,应根据数据的需求和业务场景选择合适的备份策略。

六、 高可用

在生产环境中,MySQL的高可用性变得越来越重要,因为它是一个核心的数据存储和管理系统,任何错误或中断都可能导致严重的数据丢失和系统瘫痪。因此,建立高可用的MySQL环境是至关重要的。

6.1 MMM

MMM来进行MySQL集群的自动监控和故障转移。

MMM是一种MySQL集群的高可用性解决方案,它使用虚拟IP(VIP)机制来实现MySQL集群的高可用。所谓虚拟IP,就是一个虚拟的IP地址,它并不属于任何一台实体机器,而是掌管着整个集群的管理,当主节点出现故障时,这个虚拟IP会从原主节点漂移到其他节点,由这些节点继续提供MySQL服务,这样就可以保证MySQL集群的高可用性和可靠性。

以一个网站为例子,假设这个网站有很多用户,而这些用户都需要使用MySQL数据库来获取数据。如果MySQL服务器挂了,那么这个网站的所有用户都将无法继续使用MySQL数据库,这将会对网站的运营产生非常严重的影响。但是,如果这个网站采用了MMM来进行MySQL集群的自动监控和故障转移,那么当主节点出现故障时,这个虚拟IP会从原主节点漂移到其他节点,由这些节点继续提供MySQL服务,这样就可以保证网站的MySQL数据库一直可以正常服务,不会出现故障,网站的用户也不会因此受到影响。

虽然MMM可以保证MySQL集群的高可用性,但是它也有一些缺点。最主要的缺点就是故障转移过程过于简单粗暴,容易丢失事务,这将导致MySQL数据出现不一致的情况。因此,建议在使用MMM的同时,采用半同步复制来降低失败概率。半同步复制可以实现主节点执行SQL语句后,至少有一个从节点同步完成之后再返回成功信息,这样可以保证数据更加一致性,减少数据丢失的可能性。

总之,MMM是一种非常有用的解决方案,可以保证MySQL集群的高可用性,但是如果我们想要更好的数据一致性和可靠性,建议在使用MMM的同时,采取一些其他的措施,如半同步复制等,这样才能更好地保证MySQL数据的安全和可靠性。

6.2 MHA

你是否曾经体验过使用某个软件或者网站时,突然出现了错误提示或者无法正常使用的情况?这就是所谓的故障了。而在技术领域,故障也是经常会出现的问题。例如,当你使用MySQL数据库时,可能会出现主节点出现故障的情况,导致数据无法正常操作。

那么,有没有一种工具能够在出现故障时,可以自动地进行故障切换,并在切换过程中最大程度地保证数据的一致性呢?答案是肯定的,这就是我们今天要介绍的高可用性与可伸缩性(MHA)工具。

MHA可以理解为是一种数据库自动故障切换工具。当MySQL主节点出现故障时,它可以自动地将一个从节点提升为新的主节点,并确保其他从节点与新主节点的数据一致性。同时,MHA能够在30秒内完成故障切换过程,确保数据库的可用性和数据完整性。

举个例子,假设你的网站正在使用MySQL存储用户数据,其中有一个主节点和两个从节点。在某一天,主节点出现了故障,无法正常工作。如果你使用MHA工具,它可以自动地检测主节点的故障,并将其中一个从节点提升为新的主节点。这样,你的网站可以继续正常工作而不会出现数据的混乱和错误。

那么MHA工具是如何实现自动故障切换的呢?其实原理就是检测到主节点的故障后,MHA会自动将其中一个从节点提升为新的主节点,并让其他从节点挂载到新的主节点上。同时,在切换过程中,MHA还会自动地获取其他从节点的额外信息,以确保数据的一致性。这样,就能够在最短的时间内恢复数据库的可用性,避免因故障导致的数据损失。

MHA工具的部署也比较简单,可以将其分为Manager节点和Node节点两部分。Manager节点可以单独部署在一台独立的机器上,用于探测Node节点并判断各节点的运行状况。而Node节点则需要部署在每一台MySQL机器上,负责解析MySQL日志和提供故障切换的功能。

总的来说,MHA工具是一种非常实用的自动故障切换工具,它能够在故障发生时自动地提升从节点为新的主节点,并确保数据的一致性和可用性。同时,MHA工具的部署也比较简单,可以分为Manager节点和Node节点两部分。无论是对于企业还是个人用户,MHA工具都可以帮助你更好地保障数据库的稳定性和可用性,避免因故障而造成的损失。

6.3 MGR

你听说过一个有趣的故事吗?有一天,一个名叫Tom的MySQL数据库管理员,在半夜被一封报警邮件吵醒了。原来,他管理的数据库出现了数据不一致的问题,因为MySQL的异步复制和半同步复制机制存在漏洞。这让Tom十分苦恼,因为他深知数据的一致性对于数据库的稳定性和安全性有多么重要。而在这个半夜,Tom终于找到了一个解决问题的办法——组复制(MGR)。

组复制是MySQL官方在5.7.17版本中正式推出的一种复制机制。它由若干个节点组成一个复制组,其中一个节点是主节点,其余节点是从节点。当主节点执行事务时,必须经过超过半数节点的决议并通过后才能提交。这样就可以解决异步复制和半同步复制中可能产生的数据不一致问题。

组复制的优点是基本无延迟,延迟比异步复制小很多,且具有数据强一致性,可以保证事务不丢失。比如,假设有三个节点组成一个复制组,其中一个节点是主节点,其他两个节点是从节点。当主节点执行一个事务时,必须至少有两个节点认可该事务后,该事务才能提交。这样可以避免数据不一致的问题。而对于异步复制和半同步复制,由于它们只需要一个节点认可就可以提交,所以可能会出现数据不一致的问题。

但是,组复制也存在一些局限性。首先,它仅支持InnoDB存储引擎,如果你的数据库使用其他存储引擎,就无法使用组复制。其次,每个表必须具有主键,否则无法进行数据复制。最后,组复制仅支持GTID模式,日志格式为row格式,这也会对一些用户造成困扰。

尽管组复制存在一些限制,但它的优点还是非常明显的。Tom终于找到了解决异步复制和半同步复制问题的办法,并且在使用组复制后,他再也没有被半夜的报警邮件吵醒了。组复制让Tom睡得更安稳,也让数据的一致性得到了更好的保障。

七、 异常发现处理

在使用MySQL时,可能会遇到各种异常情况,例如连接错误、查询错误、数据删除错误等等。在处理这些异常情况时,开发人员需要了解异常的原因和处理方法,以便及时排除问题,保障系统的稳定性和可靠性。

7.1 数据库监控

📝 数据库监控的功能介绍
🔥 1. 实时监控

数据库监控工具可以实时监控数据库的运行状况,对于SQL的执行和运行状态进行实时监控。管理员可以及时发现和排查慢查询、长时间连接和死锁等问题,及时做出解决方案,保证系统稳定运行。

🔥 2. 异常报警

数据库监控工具可以及时将数据库异常通过短信、邮件、微信等形式通知给管理员,管理员可以随时关注数据库的运行状况,及时发现异常情况,采取措施避免数据库出现故障和数据的损失。

🔥 3. 性能优化

数据库监控工具可以对数据库的性能进行实时分析,找出数据库性能瓶颈,发现问题并优化性能,提高数据库的运行效率和稳定性。

🔥 4. 安全管理

数据库监控工具可以提供安全性管理功能,包括对数据库的访问控制、安全审计、加密等功能,保障企业数据的安全性。

🔥 5. 统计数据与分析

数据库监控工具可以将数据库运行的实时指标统计分析图表显示出来,方便管理员进行数据分析,更好地对数据库进行规划和评估。

📝 目前主流的数据库监控工具介绍
🔥 1. Prometheus + Grafana + mysqld_exporter

Prometheus是目前最受欢迎的开源监控系统之一,它支持多种数据源,包括MySQL、PostgreSQL、Elasticsearch等。Prometheus可以通过Exporter的方式来监控MySQL的状态,mysqld_exporter就是用来监控MySQL的Exporter之一,它能够提供大量的MySQL指标,包括连接数、慢查询、锁等。而Grafana则是一个开源的监控仪表盘,可以和Prometheus结合使用,可以可视化地展示MySQL的监控数据,方便管理员进行数据分析。

🔥 2. SolarWinds SQL Sentry

SolarWinds SQL Sentry是一款功能强大的数据库监控工具,它支持多种数据库管理系统,包括MySQL、MSSQL等。SQL Sentry可以帮助管理员实时监控数据库的性能、异常状况和安全问题,提供多种分析报告,包括慢查询、死锁等,方便管理员进行数据库性能优化和安全管理。

🔥 3. Database Performance Analyzer

Database Performance Analyzer是一款针对MySQL、Oracle、MSSQL等数据库的监控和性能优化工具,它可以帮助管理员实时监控数据库性能,及时发现数据库性能问题,并提供详细的分析报告和诊断工具,方便管理员进行数据库性能优化。

🔥 4. OpenFalcon

OpenFalcon是一套企业级监控系统,它可以对MySQL、Redis、MongoDB等多种数据库进行实时监控和数据分析。OpenFalcon可以提供多种MySQL指标,包括QPS、连接数、慢查询等,并提供图表展示,方便管理员进行数据分析和性能优化。

7.2 数据库日志

MySQL中有许多关键的日志可以用来发现异常并给出解决方案。这些日志记录了MySQL的各种操作和变化,可以帮助开发人员和管理员快速定位问题和解决问题。

📝 1. 重做日志

重做日志(redo log)主要用来记录物理级别的页修改操作。例如,当我们向页号123的偏移量456写入了“789”数据时,重做日志就会记录下这个修改操作。重做日志可以通过命令“show global variables like ‘innodb_log%’;”来查看。重做日志主要用于事务提交时,保证事务的持久性和回滚。

举个例子,如果我们在执行一个更新操作时突然断电,导致MySQL服务器意外关闭,此时因为重做日志的存在,我们可以通过重做日志来恢复数据。因为重做日志记录了所有的页修改操作,我们可以根据这些修改操作把数据还原到最新的状态。

📝 2. 回滚日志

回滚日志(undo log)主要用来记录逻辑操作日志。例如,当我们向数据库添加一条记录时,会记录一条相反的删除操作。回滚日志可以通过命令“show variables like ‘innodb_undo%’;”来查看。回滚日志主要用于保证事务的原子性,在需要时回滚事务。

举个例子,如果我们执行一条SQL语句时遇到一个错误,导致事务不能正常完成,此时我们可以使用回滚日志来回滚事务。回滚日志记录了所有的逻辑操作,我们可以根据这些操作来还原到之前的状态。

📝 3. 变更日志/二进制日志

变更日志/二进制日志(bin log)主要记录了数据库执行的数据定义语句(DDL)和数据操作语句(DML)等操作。例如,当我们创建一个新的表或者更新一条记录时,变更日志就会记录下这个操作。变更日志可以通过命令“show variables like ‘%log_bin%’;”来查看。变更日志主要用于性能优化和复制数据。

举个例子,如果我们在一台MySQL服务器上进行了一些操作,现在需要在另一台MySQL服务器上同步这些操作,这时候变更日志就会发挥作用。我们可以将变更日志复制到另一台服务器,并根据这些操作指令来恢复数据。

📝 4. 慢查询日志

慢查询日志记录了响应时间超过指定阈值的SQL语句。慢查询日志可以通过命令“show variables like ‘%slow_query_log%’;”来查看。慢查询日志主要用于性能优化。

举个例子,如果我们的网站用户反映页面响应速度很慢,我们可以通过慢查询日志来查找到响应时间较长的SQL语句,并对这些SQL语句进行优化,提高网站的响应速度。

📝 5. 错误日志

错误日志记录了MySQL服务启动、运行、停止时的诊断信息、错误信息和警告提示。错误日志可以通过命令“SHOW VARIABLES LIKE ‘log_err%’;”来查看。错误日志主要用于排查MySQL服务出现异常的原因。

举个例子,如果我们的MySQL服务不能正常启动,我们可以通过错误日志来查找到具体的错误信息,并针对错误信息进行处理,从而解决问题。

📝 6. 通用查询日志

通用查询日志记录了用户的所有操作,无论是所有的SQL语句还是调整MySQL参数或者启动和关闭MySQL都会记录。通用查询日志可以通过命令“SHOW VARIABLES LIKE ‘%general%’;”来查看。

举个例子,如果我们想要还原某个操作的场景,可以通过通用查询日志来查找到具体的操作指令,从而还原操作的场景。

📝 7. 中继日志

中继日志(relay log)只存在主从数据库的从数据库上,用于主从同步,可以在xx-relaybin.index索引文件和-relaybin.0000x数据文件查看。

举个例子,如果我们的MySQL服务器使用了主从同步,那么中继日志就会发挥作用。在主服务器上进行操作后,这些操作会被记录在二进制日志中,然后通过中继日志传输到从服务器上,以实现主从同步。

📝 8. 数据定义语句日志

数据定义语句日志(ddl.log)记录了数据定义的SQL,比如ALTER TABLE。数据定义语句日志主要用于记录数据库结构的变化。

举个例子,如果我们需要知道某个表的创建时间或者修改时间,可以通过查看数据定义语句日志来获取这些信息。

📝 9. processlist日志

processlist日志记录了正在执行的SQL语句。processlist日志可以通过命令“show processlist;”或者“select * from information_schema.processlist;”来查看。

举个例子,如果我们发现MySQL服务器的负载很高,我们可以通过查看processlist日志来查找到具体的SQL语句,从而找到造成负载高的情况。

📝 10. innodb status日志

innodb status日志可以查看事务、锁、缓冲池和日志文件,主要用于诊断数据库性能。innodb status日志可以通过命令“show engine innodb status;”来查看。

举个例子,如果我们发现MySQL服务器的性能很差,我们可以通过查看innodb status日志来查找到具体的性能瓶颈,从而进行优化。

7.3 数据库巡检

数据库巡检是保证系统平稳有效运行的重要工作,就如同飞机起飞前巡检可保证飞机后续顺利运行。而巡检工作主要由数据库管理员和后端开发工程师负责。他们各自负责的巡检内容也是不同的。

首先是数据库管理员的巡检工作。数据库管理员主要负责处理数据库基础功能,例如数据库的安装、配置和初始化,并监控数据库的运行状态,以便及时发现并解决可能发生的故障。此外,数据库管理员还负责管理数据库的高可用性,包括设置备份计划、配置容错性和灾备系统等,以保证数据库在发生故障时能够快速恢复,并保证数据的完整性和一致性。

除此之外,数据库管理员还需要处理中间件和报警组件。中间件是数据库服务器和客户端之间的“桥梁”,常见的中间件包括连接池、缓存、代理等。数据库管理员需要对中间件进行巡检,确保其正常运行,以提高数据库的性能和可靠性。报警组件则是用于监控系统的各个方面,包括CPU、内存、磁盘空间等,一旦出现异常情况就会立即通知管理员,以便及时处理。

另外,数据库管理员还需要对集群拓扑进行巡检。集群拓扑主要包括数据库实例的分布、分片策略、容错等,需要确保集群的高度可用性。此外,数据库管理员还需要针对数据库的核心参数进行巡检,例如缓存大小、SQL缓存池大小、连接池大小等。这些参数的设置直接影响到数据库的性能,需要根据实际情况进行调整。

除了以上内容,数据库管理员还需要对服务器硬件层面的隐患进行巡检,例如磁盘可用空间预测等范围。这些都是为了确保数据库的稳定性和可靠性。

接下来是后端开发工程师的巡检工作。后端开发工程师主要负责库表设计缺陷、数据库使用不规范等引起的业务故障或性能问题的隐患。例如,如果数据库中的某个表存在设计缺陷,或者存储了大量无用数据,就会导致查询速度缓慢,从而影响业务效率。因此,后端开发工程师需要加强对库表设计和数据清理的巡检。

此外,后端开发工程师还需要定期采集整型字段值有没有超过最大值。因为对于整型类型的字段,保存的数值有上限,如果超过了这个范围就会导致数据不准确,从而影响业务。因此,需要对这些字段进行巡检。

除此之外,后端开发工程师还需要对读写情况进行巡检。例如,需要定期观察表大小,找出有问题的大表进行优化调整。如果某个表的数据量过大,就需要考虑分表或者分区策略,以提高查询效率和降低负载压力。

最后,需要强调的是,数据库巡检是一项长期的工作,需要不断地进行调整和优化。只有加强巡检工作,才能保证数据库系统的稳定性和可靠性,从而保障业务的顺利运行。

7.4 资源评估

压测是数据库测试中不可或缺的一环。这项测试能够模拟数据库在高负载的情况下的表现,测试人员会将大量数据输入数据库中,甚至会人为地模拟一些异常情况,比如网络超时、硬盘故障等。测试人员会根据测试结果来评估数据库的性能,例如响应时间、吞吐量、并发性等指标。如果测试结果达到了预期的性能指标,那么数据库就可以被认为是稳定可靠的。

但压测的测试环境与实际生产环境存在很大的差异,所以测试人员还需要结合实际的业务场景进行策略性的压测。比如,一个电商网站在双11期间需要处理的数据量必定会比其平时业务量的几倍还多,因此测试人员就需要根据双11期间的业务情况来进行相应的压测,以确保数据库能够承受得住这样的高流量压力。

除了压测,还有一个重要的测试手段就是性能测试。性能测试主要是评估数据库在各种条件下的性能表现,包括硬件性能和软件性能。例如,测试人员会模拟多用户场景,在同一时间内对数据库进行查询、插入、更新等操作,以评估数据库在高并发场景下的性能表现。如果测试结果符合预期,数据库可以被认为是具有高可靠性和高响应速度的。

而在数据库容量评估方面,运维工程师和数据库管理员扮演着非常重要的角色。他们需要评估当前数据库的存储容量是否足够,如果不足,需要考虑扩容。在扩容之前,需要仔细规划服务器资源,以确保系统可以满足扩容后的需求,避免扩容后出现新的问题。同时,还需要设置预警通知,一旦数据库容量超过了阈值,就需要及时通知相关人员进行扩容。

例如,一家电商网站需要考虑在双11期间的业务量,可以根据历史数据和预测模型来估算当前的存储容量是否能够满足需求。如果不足,就需要安排运维人员进行扩容,同时设置预警通知,以免影响网站的正常运行。

除了以上的测试和评估手段,还有一些辅助工具和技术可以提高数据库的稳定性和可靠性。例如,备份和恢复技术可以确保在数据库出现故障或意外情况时能够快速恢复数据。监控和警报系统可以帮助管理员及时掌握数据库的运行状态和异常情况,及时采取措施解决问题。自动化运维系统可以自动化部署、配置、监控和管理数据库,减少人为操作的错误和疏漏。

总之,保证数据库的稳定性和可靠性是任何企业、政府和组织的核心之一。通过各种测试、评估和技术手段,可以确保数据库在极限环境下也能正常工作,避免数据丢失和业务中断,从而保障业务的顺利进行。

八、 数据服务

数据服务,简单地说,是一种帮助用户管理、保护和验证数据的服务。你可以把它想象成是一名保姆,关注并照顾着你的数据,帮你处理一切与数据有关的问题,让你安心工作,放心使用。

数据服务的主要目的就是帮助用户管理他们的数据。他们可以使用数据服务来规划和迁移数据。举个例子来说,假设你是一家公司的IT管理员,你需要将公司的所有数据从一台服务器迁移到另一台服务器上。这项任务过于繁琐,你可能需要把许多文件和文件夹一一复制并粘贴到新的服务器上。但如果你使用数据服务,它可以帮助你轻松地完成这项任务,让你在短时间内快速迁移数据,从而省去了大量时间和精力。

此外,数据服务还可以帮助用户备份和恢复数据库。如果你是一名数据分析师,你的工作需要许多复杂的数据处理。在这个过程中,你需要确保你的数据库经常备份,以防止数据丢失。如果你不小心误删了某个文件或数据库坏了,数据服务可以帮助你轻松地恢复数据,让你的工作不受影响。

最后,数据服务还可以用于数据校验。如果你是一名网站管理员,你需要确保你的网站数据是完整的、准确的和一致的。如果你的数据出现错误,可能会导致用户无法访问你的网站,甚至可能会影响你的业务。数据服务可以帮助你定期检查数据,确保它没有任何问题。

总之,数据服务是一项非常重要的服务,可以帮助你轻松地管理、保护和验证你的数据。它可以让你的工作更轻松、更有效率,让你更加放心使用你的数据。

8.1 子表结构生成

一个表进行拆分,会根据业务实际情况进行拆解,例如用户表可以根据地区拆分tb_user可拆分成上海地区的用户表(tb_user_sh)、广州地区的用户表(tb_user_gz),那么全国有很多个城市,每个地方都需要创建一张子表并且维护它会比较费时费力,通常情况下,会开发3个接口做表结构同步:根据主表创建子表、主表字段同步到子表、主表索引同步子表。

下面对这3个接口提供思路以及关键代码。

主表创建子表,代码如下: 

/**
* {
*     "tableName": "tb_user",
*     "labCodes": [
*         "sh",//上海
*         "gz"//广州
*     ]
* }
*/
public Boolean createTable(ConfigReq reqObject) {
  if (CollectionUtils.isEmpty(reqObject.getLabCodes())) {
    return false;
  }
  List<String> labCodes = reqObject.getLabCodes();
  for (String labCode: labCodes){
    //主表表名
    String tableName = reqObject.getTableName();
    //子表后表名
    String newTable = String.format("%s_%s", tableName, labCode);
    //校验子表是否存在
    Integer checkMatrix = configExtMapper.checkTable(newTable);
    if(checkMatrix == null || checkMatrix.intValue() < 0){
    //创建子表结构
    configExtMapper.createConfigTable(tableName, newTable);
    }
    }
  return true;
}

主表字段同步到子表,代码如下:

/**
* 主表字段同步到子表
* @param masterTable 主表
* @return
*/
private Boolean syncAlterTableColumn(String masterTable) {
  String table = masterTable + "%";
  //获取子表名
  List<String> tables = configExtMapper.getTableInfoList(table);
  if(CollectionUtils.isEmpty(tables)){
    return false;
  }
  //获取主表结构列信息
  List<ColumnInfo> masterColumns = configExtMapper.getColumnInfoList(masterTable);
  if (masterColumns.isEmpty()){
    return false;
  }
  String alterName = null;
  for (ColumnInfo column: masterColumns) {
    column.setAlterName(alterName);
    alterName = column.getColumnName();
  }
  for(String tableName : tables){
    if(StringUtils.equalsIgnoreCase(tableName, masterTable)){
      continue;
    }
    //获取子表结构列信息
    List<ColumnInfo> columns = configExtMapper.getColumnInfoList(tableName);
    if(CollectionUtils.isEmpty(columns)){
      continue;
    }
    for (ColumnInfo masterColumn : masterColumns) {
      ColumnInfo column = columns.stream().filter(c -> StringUtils.equalsIgnoreCase(c.getColumnName(),
      masterColumn.getColumnName())).findFirst().orElse(null);
      if (column == null){
        column = new ColumnInfo();
        column.setColumnName(masterColumn.getColumnName());//列名
        column.setAddColumn(true);//是否修改
      }
      if (column.hashCode() == masterColumn.hashCode()){
        continue;
      }
      column.setTableName(tableName);//表名
      column.setColumnDef(masterColumn.getColumnDef());//是否默认值
      column.setIsNull(masterColumn.getIsNull());//是否允许为空(NO:不能为空、YES:允许为空)
      column.setColumnType(masterColumn.getColumnType());//字段类型(如:varchar(512)、text、bigint(20)、datetime)
      column.setComment(masterColumn.getComment());//字段备注(如:备注)
      column.setAlterName(masterColumn.getAlterName());//修改的列名
      //创建子表字段
      configExtMapper.alterTableColumn(column);
    }
  }
  return true;
}

主表索引同步子表,代码如下:

/**
* 主表索引同步子表
* @param masterTableName 主表名
* @return
*/
private Boolean syncAlterConfigIndex(String masterTableName) {
  String table = masterTableName + "%";
  //获取子表名
  List<String> tableInfoList = configExtMapper.getTableInfoList(table);
  if (tableInfoList.isEmpty()){
    return false;
  }
  // 获取所有索引
  List<String> allIndexFromTableName = configExtMapper.getAllIndexNameFromTableName(masterTableName);
  if (CollectionUtils.isEmpty(allIndexFromTableName)) {
    return false;
  }
  for (String indexName : allIndexFromTableName) {
    //获取拥有索引的列名
    List<String> indexFromIndexName = configExtMapper.getAllIndexFromTableName(masterTableName, indexName);
    for (String tableName : tableInfoList) {
      if (!tableName.startsWith(masterTableName)) {
        continue;
      }
      //获取索引名称
      List<String> addIndex = configExtMapper.findIndexFromTableName(tableName, indexName);
      if (CollectionUtils.isEmpty(addIndex)) {
        //创建子表索引
        configExtMapper.commonCreatIndex(tableName, indexName, indexFromIndexName);
      }
    }
  }
  return true;
}
子表结构生成的SQL,代码如下:
<!--校验子表是否存在 这里db_user写死了数据库名称,后面可以根据实际情况调整-->
<select id="checkTable" resultType="java.lang.Integer" >
  SELECT 1 FROM INFORMATION_SCHEMA.`TABLES` WHERE TABLE_SCHEMA = 'db_user' AND TABLE_NAME = #{tableName};
</select>
<!--创建子表结构-->
<update id="createConfigTable" >
  CREATE TABLE `${newTableName}` LIKE `${sourceName}`;
</update>
<!--获取子表名-->
<select id="getTableInfoList" resultType="java.lang.String">
  SELECT `TABLE_NAME`
  FROM INFORMATION_SCHEMA.`TABLES`
  WHERE `TABLE_NAME` LIKE #{tableName};
</select>
<!--获取主/子表结构列信息 这里db_user写死了数据库名称,后面可以根据实际情况调整-->
<select id="getColumnInfoList" resultType="com.yunxi.datascript.config.ColumnInfo">
  SELECT `COLUMN_NAME` AS columnName
  ,COLUMN_DEFAULT AS columnDef   -- 是否默认值
  ,IS_NULLABLE AS isNull    -- 是否允许为空
  ,COLUMN_TYPE AS columnType    -- 字段类型
  ,COLUMN_COMMENT AS comment      -- 字段备注
  FROM INFORMATION_SCHEMA.`COLUMNS`
  WHERE TABLE_SCHEMA = 'db_user'
  AND `TABLE_NAME` = #{tableName}
  ORDER BY ORDINAL_POSITION ASC;
</select>
<!--创建子表字段-->
<update id="alterTableColumn" parameterType="com.yunxi.datascript.config.ColumnInfo">
  ALTER TABLE `${tableName}`
  <choose>
    <when test="addColumn">
      ADD COLUMN
    </when >
    <otherwise>
      MODIFY COLUMN
    </otherwise>
  </choose>
  ${columnName}
  ${columnType}
  <choose>
    <when test="isNull != null and isNull == 'NO'">
      NOT NULL
    </when >
    <otherwise>
      NULL
    </otherwise>
  </choose>
  <if test="columnDef != null and columnDef != ''">
    DEFAULT #{columnDef}
  </if>
  <if test="comment != null and comment != ''">
    COMMENT #{comment}
  </if>
  <if test="alterName != null and alterName != ''">
    AFTER ${alterName}
  </if>
</update>
<!--获取所有索引-->
<select id="getAllIndexNameFromTableName" resultType="java.lang.String">
  SELECT DISTINCT index_name FROM information_schema.statistics WHERE table_name = #{tableName} AND index_name != 'PRIMARY'
</select>
<!--获取拥有索引的列名-->
<select id="getAllIndexFromTableName" resultType="java.lang.String">
  SELECT COLUMN_NAME FROM information_schema.statistics WHERE table_name = #{tableName} AND index_name = #{idxName} AND index_name != 'PRIMARY'
</select>
<!--获取索引名称-->
<select id="findIndexFromTableName" resultType="java.lang.String">
  SELECT index_name FROM information_schema.statistics WHERE table_name = #{tableName} AND index_name = #{idxName}
</select>
<!--创建子表索引-->
<update id="commonCreatIndex">
  CREATE INDEX ${idxName} ON `${tableName}`
  <foreach collection="list" item="item" open="(" close=")" separator=",">
    `${item}`
  </foreach>;
</update>

根据以上关键代码以及实现思路结合实际情况开发出3个接口足以满足日常分表需求了。

8.2 数据迁移

数据迁移通常有两种情况。第一种情况是开发人员手动编写代码,将数据从一个数据库读取出来,再将数据异步的分批次批量插入另一个库中。这需要开发人员具有一定的编码能力,还需要考虑如何处理数据丢失和重复插入的问题。当然,这种方式也可以通过一些第三方工具来帮助开发人员完成。比如,我们可以使用Python的Pandas库读取数据,然后使用SQLAlchemy库写入数据到另一个数据库中。

第二种情况是通过数据库迁移工具来完成数据迁移。目前比较常用的工具就是Navicat for MySQL。使用这个工具可以方便地将数据从一个数据库导出,然后再将数据导入到另一个数据库中。这种方式相对来说比较方便,不需要开发人员编写代码,但是需要注意不同数据库语法和实现的不同。此外,数据库版本不同,分库分表时数据库的自增主键ID容易出现重复键的问题,我们需要考虑分布式主键生成策略来解决这个问题。

数据迁移还需要注意一些细节问题。比如,我们需要备份好原先的数据库,以防止出现数据丢失等问题。此外,在进行大规模数据迁移时,我们需要选择合适的时间去进行迁移,避免对业务产生影响。

在进行数据迁移时,我们还需要考虑一些性能问题。比如,我们可以将数据分批次进行迁移,并控制每个批次的大小,以充分利用服务器资源,提高数据迁移的效率。此外,我们还需要注意网络带宽的问题,避免大量数据传输导致网络拥堵。

8.3 数据校验

数据校验是指对数据进行检查、验证、过滤等操作,从而确保数据的有效性、正确性和合法性。在软件开发的过程中,数据校验是非常重要的一环,因为合理的数据校验可以大大减少错误和异常的发生,保证系统的稳定性和安全性。

数据校验的具体实现可以分为两类:前端传入参数的数据校验和程序插入数据库中的数据的校验。

📝 前端传入参数的数据校验

在前端传入参数时,需要对传入的数据进行校验,确保数据的格式和值的正确性。这有利于减少后端的数据校验时间和提高系统的响应速度。常见的参数校验包括:

  1. 非空校验:检查传入参数是否为空值,如果为空,则返回错误信息提示用户输入正确的参数。
  • 例如,当用户注册时,需要填写用户名和密码等信息,如果用户没有填写必填项,则系统会提示用户“必填项不能为空”。
  1. 长度校验:检查传入参数的长度是否符合要求,如果长度超出范围,则返回错误信息提示用户修改。
  • 例如,当用户注册时,密码的长度需要在6-18位之间,如果用户输入的密码长度不符合要求,则系统会提示用户“密码长度应在6-18位之间”。
  1. 类型校验:检查传入参数的数据类型是否正确,例如,传入的是数字还是字符类型,如果类型不符,则返回错误信息提示用户输入正确的参数类型。
  • 例如,当用户进行邮件订阅时,需要输入邮箱地址,系统会检查输入的邮箱地址是否为正确的邮箱格式,如果不符合,则系统会提示用户“请输入正确的邮箱地址”。
  1. 值的范围校验:检查传入参数的值是否在合法范围内,如果不在,则返回错误信息提示用户输入正确的参数值。
  • 例如,当用户进行商品下单时,需要填写商品数量,系统会检查用户填写的商品数量是否大于库存数量,如果数量超出范围,则系统会提示用户“商品数量不足”。
📝 程序插入数据库中的数据的校验

在程序插入数据库中的数据时,需要对数据进行校验,确保数据的完整性和正确性。常见的校验包括:

  1. 非空校验:在程序插入数据库中的数据时,需要对必填项进行非空校验,如果为空,则插入失败,返回错误信息提示程序员输入正确的数据。
  • 例如,当系统添加新的商品时,需要输入商品名称、价格、库存等信息,如果其中有必填项为空,则系统会提示程序员输入正确的数据。
  1. 长度校验:在程序插入数据库中的数据时,需要对数据的长度进行校验,确保数据的长度符合要求,如果超出范围,则插入失败,返回错误信息提示程序员修改数据。
  • 例如,当系统添加新的商品时,需要对商品名称进行长度校验,如果长度超出预设长度,则系统会提示程序员输入正确的数据。
  1. 类型校验:在程序插入数据库中的数据时,需要对数据的类型进行校验,确保数据的数据类型正确,如果类型不正确,则插入失败,返回错误信息提示程序员输入正确的数据。
  • 例如,当系统添加新的商品时,需要对商品价格进行类型校验,确保商品价格为数字类型,如果不是,则系统会提示程序员输入正确的数据类型。
  1. 值的范围校验:在程序插入数据库中的数据时,需要对数据的值进行范围校验,确保数据的值在合法范围内,如果不在,则插入失败,返回错误信息提示程序员输入正确的数据值。
  • 例如,当系统添加新的商品时,需要对商品库存进行校验,确保商品库存数量大于等于0,如果库存小于0,则系统会提示程序员输入正确的数据值。
📝 数据迁移的源数据库和目标数据库的表数据的对比

数据迁移是指将一个系统的数据迁移到另一个系统中,并确保数据的完整性和正确性。在数据迁移的过程中,需要对源数据库和目标数据库的表数据进行对比,确保数据的一致性和正确性。常见的对比包括:

  1. 数据完整性校验:在数据迁移的过程中,需要对源数据库和目标数据库的表数据进行完整性校验,确保两个数据库的数据一致性。
  • 例如,当将一个公司的员工信息从一个系统迁移到另一个系统时,需要对两个系统的员工信息进行完整性校验,确保员工数量和员工信息一致。
  1. 数据格式校验:在数据迁移的过程中,需要对源数据库和目标数据库的数据格式进行校验,确保数据格式的一致性和正确性。
  • 例如,当将一个系统中的英文数据迁移到一个系统中,需要对数据格式进行校验,确保数据的格式正确并且可以被目标系统正确识别。
  1. 数据类型校验:在数据迁移的过程中,需要对源数据库和目标数据库的数据类型进行校验,确保数据类型的一致性和正确性。
  • 例如,当将一个系统中的时间类型数据迁移到另一个系统中时,需要对时间格式进行校验,确保时间格式的一致性和正确性。
  1. 数据范围校验:在数据迁移的过程中,需要对源数据库和目标数据库的数据范围进行校验,确保数据的范围正确并且不超出系统的限制范围。
  • 例如,当将一个系统中的审核数据迁移到另一个系统中时,需要对审核数据的范围进行校验,确保数据的范围符合目标系统的审核规则。

8.4 总结

数据校验在软件开发中是非常重要的一环,它可以有效地减少错误和异常的发生,保证系统的稳定性和安全性。数据校验涉及到前端传入参数的数据校验、程序插入数据库中的数据的校验和数据迁移的源数据库和目标数据库的表数据的对比等方面。在具体实现时,需要根据实际情况进行合理的数据校验,确保数据的有效性、正确性和合法性。

九、 读写分离

MySQL读写分离是数据库优化的一种手段,通过将读和写操作分离到不同的数据库服务器上,可以提高数据库的读写性能和负载能力。

9.1 主从数据同步

主从数据同步,顾名思义,就是将主数据库上的数据同步到从数据库上。主库负责写操作,从库负责读操作和备份。通过同步机制,从库能够维护与主库相同的数据状态,并能够主动地拉取主库上的新数据,保证数据一致性和高可用性。

然而,在实际的应用场景中,由于主从数据同步需要耗费时间和资源,会产生一定的延迟,从而影响到业务的吞吐量和用户体验。

一般来说,数据同步是通过异步方式进行的,即主库写入数据后,通过网络将数据同步给备库。但是这种方式存在一些缺点,比如备库中的数据可能比主库中的数据要旧一些,如果出现了主库宕机的情况,备库中的数据就不能再同步了。为了解决这些问题,我们可以将主从数据同步方案修改为同步方式,这样主库上的写才能返回。

首先,当业务系统发起写操作时,数据会被写入主库。但是,写请求需要等待主从同步完成才能返回,也就是说,主库中的数据必须与备库中的数据同步完成后,才能返回成功。这样可以保证备库中的数据与主库中的数据始终保持一致,即使主库出现宕机的情况,备库中的数据仍然可以保持最新。

另外,当我们进行数据读取时,我们会从从库中读取数据,这是因为从库中的数据已经与主库同步完成,所以我们可以读取到最新的数据。这也是主从同步方案的另一个优点,即数据的读写分离,可以有效地减轻主库的负担。

但是,这种同步方案也存在一些缺点。比如,当数据写入主库后,需要等待主从同步完成才能返回,这会增加写请求的时延,从而导致吞吐量的降低。另外,如果备库中的数据发生了错误,也会影响主库中的数据,有时会出现整个系统崩溃的情况。

因此,在使用主从同步方案时,我们需要进行权衡,权衡它的优缺点,并尽量采用合适的方案来保证数据的安全和稳定性。

image

📝 优化业务读写访问

在使用主从数据同步时,我们可以考虑优化业务的读写访问方式,从而减少对主库的写访问次数,降低延迟干扰。具体来说,可以采用以下两种方法:

🔥 1. 读写分离

读写分离是一种常见的优化方式,它将读操作和写操作分别分配到主数据库和从数据库上进行。对于大量的读操作,我们可以通过从数据库来处理,从而减轻主数据库的负担,减少写操作对主数据库的干扰。

🔥 2. 批量提交

批量提交是指将多个写操作收集在一起,通过一次性提交的方式来减少对主数据库的写访问次数。采用批量提交的方式,可以将多个写操作合并在一起,减少主数据库的响应时间,提高写操作的效率。

例如,我们在一个网上商城中增加了一个新的商品,如果每次添加商品都要向主库提交一次写请求,那么将会导致主库的响应速度变慢,并且增加了主数据库的负担。通过批量提交的方式,我们可以将多个商品的写请求合并在一起,一次性提交给主库,从而减少主库的写访问次数,提高写操作的效率。

📝 增加从库数量

增加从库的数量的主要作用是提高读取性能和容错能力,尤其是在高并发的情况下。当主库繁忙时,从库可以承担一部分读取请求,减轻主库的负担,从而提高系统的整体性能和可靠性。而在从库出现故障时,其他从库可以继续提供服务,从而避免系统的宕机风险。

📝 采用异步同步方式

在主从数据同步中,同步数据的方式分为同步和异步两种方式。同步方式指当主库写入数据后,需要等待所有从库都完成同步后才能返回。这种方式比较安全,但是会影响读写性能。异步方式则是指主库写入数据后立即返回,然后再异步地将数据传输给从库进行同步。这种方式虽然会存在数据损失的风险,但是可以提高数据同步的效率,降低延迟的影响,从而提高系统的吞吐量和性能。

例如,在一个社交平台中,用户发布的内容需要被同步到所有关注该用户的粉丝的首页中。如果采用同步方式进行数据同步,那么在用户发布内容时,需要等待所有粉丝的首页都完成同步才能返回。这样会导致延迟问题的影响,降低系统的性能。通过采用异步方式进行数据同步,可以在用户发布内容时立即返回,从而提高系统的吞吐量和性能。

9.2 中间件路由

中间件路由就是在读写分离的基础上,为了更好地利用资源,让我们的应用程序在读写过程中更加智能地调度读操作和写操作。

就拿一个网上购物的例子来说吧。当我们在网上购物时,我们肯定不希望我们看到的商品数量与实际库存不符。如果我们看到了一个商品,把它加入到购物车,但是这个商品实际已经卖光了,那么我们就会非常不爽。为了避免这种情况,我们需要读写分离的中间件路由。

中间件路由的原理是这样的:当我们的应用程序发起写请求时,中间件会把数据发往主库,并记录下这个写操作的key(例如操作表加主键)。当我们的应用程序发起读请求时,中间件会先判断这个key是否存在,如果存在,则暂时路由到主库,在主库中读取数据。在一定时间过后,中间件会认为主库和从库的同步已经完成了,就会删除这个key,此后的读操作就会从从库中读取数据,避免了延迟的问题。

如果你还不理解,不要紧,这里有一个例子来帮助你理解。假设我们要查询用户的订单信息,我们的读写分离的中间件路由可能会这样处理:

  1. 应用程序发起写请求,向主库中写入一个订单信息,并记录下订单信息对应的key。
  2. 一段时间后,应用程序发起读请求,中间件发现对应key存在,暂时路由到主库,从主库中读取订单信息。
  3. 一段时间后,中间件认为主从同步已经完成,就会删除这个key,此后的读操作就会从从库中读取数据,避免了延迟的问题。

如此一来,我们就不用担心我们看到的东西不是实时的,而且还能够有效地避免延迟问题,提高我们的用户体验。

image

9.3 缓存路由

缓存路由是一种在数据库读写请求时使用的技术,它与中间件路由类似,都是用来优化数据库性能的。具体来说,缓存路由是将读请求分流到从库和主库中,以便更好地利用数据库资源,提高数据库的读数据的效率。

那么,现在让我们来看一下缓存路由的工作原理。首先,当业务应用发起写请求时,数据将会被发往主库中,同时缓存也会记录下这个操作的key,并设置一个缓存失效时间。这个失效时间一般设置为主从复制完成的延时时间,以保证数据的最新性。

当读请求到达缓存路由时,缓存路由会首先判断这个key是否存在。如果存在,那么请求会暂时路由到主库中。这是因为在数据库的主从复制中,主库是最新的数据来源,而且它也是负责写操作的。因此,我们需要保证数据的一致性,避免在从库中读取了旧的数据。

如果缓存中不存在这个key,那么缓存路由会考虑一些其他因素,例如近期有没有发生写操作等等。如果近期有写操作,那么请求会暂时路由到主库中;如果近期没有写操作,那么请求会暂时路由到从库中。

这样,通过使用缓存路由,我们可以将读请求合理地分流到主库和从库中,从而更好地利用数据库资源,提高数据库的读数据效率。比如,如果我们使用了缓存路由,那么在高并发的读请求时,我们可以更好地应对这种情况,提高数据库的性能。

那么,为什么需要使用缓存路由呢?实际上,缓存路由主要是为了解决数据库性能瓶颈而设计的。在高并发的情况下,数据库的读写请求可能会很多,这时,如果所有的请求都直接访问主库,那么主库的负载会非常高,从而导致系统性能下降。

因此,我们需要将读请求分流到从库中,从而减轻主库的压力。同时,在缓存中记录操作的key也可以保证数据的一致性,从而避免读取到旧的数据。这样,通过使用缓存路由,我们可以更好地利用数据库资源,提高系统的性能和响应速度。

例如,假设我们有一个电商网站,在高峰期的时候,我们可能会面临大量的读请求。当所有的请求都直接访问主库时,主库的负载会非常高,从而导致系统性能下降。但是,如果我们使用缓存路由,并将一部分读请求分流到从库中,那么我们就可以更好地利用数据库资源,提高系统的响应速度,从而改善用户体验。同时,通过设置缓存的失效时间,我们还可以保证数据的一致性,避免读取到旧的数据。

image

十、 主从复制

10.1 主从复制(replication)的工作原理

📝 主从复制的基本概念

主从复制,最基本也是最核心的概念就是备份。主从复制通过备份主服务器上的数据,将这些数据同步到从服务器上,从而达到数据备份和容错的目的。这个备份可以分为全量备份和增量备份两种方式,分别对应着不同的复制方式。

全量备份,就是在备份时把整个数据库全部备份下来。虽然可以保证数据的完整性和一致性,但是备份的时间和空间都很大,不适合频繁备份。而增量备份,就是只备份在备份之后新添加或修改的数据,以此实现备份的快速和高效。

📝 主服务器和从服务器

在主从复制中,主服务器(master)和从服务器(slave)是两个最基本的角色。主服务器是数据源,它存储着整个数据库的所有数据,并负责处理客户端的所有读写请求。而从服务器则是主服务器的一个副本,它通过从主服务器获取二进制日志,来保证自己的数据与主服务器的数据保持一致。

通常情况下,在一个主从复制的系统中,只有一个主服务器,而可以有多个从服务器。这些从服务器可以分布在不同的地方,甚至可以跨越不同的地域和网络。

📝 二进制日志和中继日志

二进制日志(binlog)和中继日志(relaylog)是主从复制中非常重要的两部分。它们在主从复制中扮演着非常重要的角色,可以帮助从服务器保持和主服务器数据的一致性。

🔥 1、二进制日志

二进制日志,也就是binlog,是MySQL数据库中一个非常重要的日志文件。它主要记录着所有的写操作,包括插入、更新和删除等,但是不包括读操作。在主从复制中,主服务器记录着所有的数据更新操作,并将这些操作记录在二进制日志中。而从服务器通过解析主服务器的二进制日志,来获取数据的更新和变化,从而保证自己的数据与主服务器的数据保持一致。

🔥 2、中继日志

中继日志,也就是relaylog,和二进制日志类似,是从服务器上的一个日志文件。在从服务器上,中继日志主要作为一个缓冲和传输的作用。从服务器通过复制主服务器的二进制日志来获取数据库的更新信息,并将这些信息存储到自己的中继日志中。接着,从服务器本身再根据这个中继日志来对自己的数据库进行更新,以达到数据的一致性。

📝 主从复制的具体实现

在了解了主从复制的基本概念和二进制日志和中继日志的作用之后,我们来看看主从复制的具体实现过程。

🔥 1、主服务器记录二进制日志

在主服务器上,当用户执行一条改变数据的SQL语句时,主服务器会在内存中保存一份修改后的数据,并把这个修改操作记录到二进制日志中。

这个修改操作会被记录在二进制日志的末尾,并按照一定的格式记录下来。这个格式主要包括事件头、事件体和事件的元数据信息等。

🔥 2、从服务器从主服务器上复制二进制日志

接下来,在从服务器上,从服务器会不断地从主服务器上获取新的二进制日志,并将这些新的日志保存到自己的中继日志中。

这个过程中,从服务器中继日志中对应的日志并不会立即执行到从服务器的数据库上,因为从服务器必须要等待主服务器完成这个事务的所有写操作,才能进行日志的应用和数据的更新。

🔥 3、从服务器重做中继日志中的日志

当从服务器收到来自主服务器的二进制日志时,从服务器会先根据二进制日志中的元数据信息,判断这是一个完整的事务,还是一个不完整的事务。如果是一个完整的事务,从服务器才会开始执行中继日志中的日志,对自己的数据库进行更新。

当从服务器正在执行中继日志中的日志时,如果出现了错误或者服务器宕机等异常情况,从服务器会自动停止执行当前的操作,并向主服务器请求重发对应的二进制日志。在主服务器上,这个重发操作是会保证事务的原子性和一致性的,因此从服务器最终也会成功地将这个事务执行完成。

📝 主从复制的优缺点

随着数据量的不断增大和业务的不断复杂化,高可用性和容错性的需求不断增强。主从复制作为一种传统的备份和容错方式,具有以下的优缺点。

🔥 1、主从复制的优点

① 数据备份和容错:主从复制通过备份主服务器上的数据,将这些数据同步到从服务器上,从而达到数据备份和容错的目的。当主服务器出现故障时,从服务器可以立即接管主服务器的工作,从而保证业务的正常运转。

② 分布式的读写操作:主从复制可以实现分布式的读写操作,即让从服务器处理读操作,而让主服务器处理写操作。这样可以有效缓解主服务器的读写压力,提升整个系统的性能和稳定性。

🔥 2、主从复制的缺点

① 数据的一致性:由于主从复制是一个异步的过程,从服务器上的数据并不能保证和主服务器的数据完全一致。如果主服务器上的数据发生了变化,而从服务器上还没有及时同步,就会导致数据不一致的情况。

② 延时和可靠性:由于主从服务器之间的网络通信和数据传输是有延时的,从服务器上的数据更新并不能保证实时性和可靠性。如果在数据传输的过程中发生了网络故障或者主服务器宕机等情况,从服务器上的数据同步也会出现问题。

10.2 主从复制bin log 日志有几种记录方式,说说各自的优缺点

首先,我们需要了解Replication模式的基本概念。在MySQL中,Replication模式通过复制数据库中的数据来实现数据备份和负载均衡。它由一个Master和多个Slave构成,其中Master是主服务器,所有修改数据的操作都在它上面进行。而Slave则是从服务器,它会复制Master上的数据并在本地进行相应的处理。其中,binlog功能是使Replication模式得以工作的关键,Master将所有的变更操作写入binlog,然后Slave通过增量获取binlog信息并在本地应用日志中的变更操作。

在Replication模式中,binlog有三种格式:SBR,RBR和mix-format Replication。每种格式都有其优缺点,我们需要根据具体情况进行选择。

首先是SBR,它是MySQL最早支持的类型,也是Replication的默认类型。SBR的优点是,因为binlog中只记录了变更操作的statements,所以日志量会很小;当使用SQL语句批量更新、删除数据时,只需记录statements即可,可以大大减少log文件对磁盘的使用;同时也因为日志量小,通过binlog恢复数据也更加快速。但是SBR的缺点也是明显的:它不能保证数据一致性,一些结果具有不确定性的操作使用SBR将会引入数据不一致的问题,比如语句中使用UDF、函数UUID()、SYSDATE()、RAND()等。此外,在InnoDB中,使用“AUTO_INCREMENT”的insert语句,将会阻塞其他“非冲突”的INSERT,因为只能串行,而这在RBR中则不需要。另外,复杂的SQL语句在Slaves上仍然需要评估解析然后才能执行,这可能会导致数据一致性的问题或者错误不断增加。

接下来是RBR,它的优点是所有的变更操作都可以被正确的复制,这是最安全的方式;对于“INSERT… SELECT”、包含“AUTO_INCREMENT”的inserts、没有使用索引的UPDATE/DELETE,相较于SBR将需要更少的行锁,意味着并发能力更强。但是RBR的缺点也很明显,它需要更多的日志量,任何数据变更操作都将被写入log,受影响的每行都要写入日志,日志包含此行所有列的值,即使没有值变更的列;因此RBR的日志条数和尺寸都将远大于SBR,特别是在批量的UPDATE/DELETE时,可能会产生巨大的log量,对性能也会带来影响。因此,我们需要权衡数据一致性和性能,选择最适合自己的日志格式。

最后是mix-format Replication,它混合了SBR和RBR的优点,选择更合适的日志格式,这取决于变更操作发生的存储引擎、statement的类型以及特征,我们建议使用mix方式。SBR和RBR都有各自的优缺点,而mix方式在兼顾数据完整性和性能方面是最佳的选择。

综上所述,对于不同的场景和需求,我们可以选择不同的binlog格式。如果数据一致性是至关重要的,那么我们可以选择RBR,SBR则适用于更新、删除等操作较少的场景。而在大多数情况下,mix-format Replication将是最好的选择。

10.3 主从复制有几种方式?

📝 异步复制

假设你是一个玩游戏的人,你和你的朋友一起玩一款游戏,玩这款游戏需要通过互联网连接服务器。你们的游戏数据都是存储在服务器上的,每一次你们在游戏中进行操作都会将这些操作发送给服务器,服务器会将这些操作记录下来,以便其他玩家也能看到你的游戏操作,这就相当于MySQL主从集群中的binlog。

在MySQL主从集群中,Master服务就相当于游戏服务器,负责处理事务并将其写入binlog,而Slave服务就相当于玩家客户端,通过binlog读取Master服务的操作记录。在游戏中,操作的响应时间是非常重要的,如果响应时间太慢,就会让人感觉很卡顿,不过这并不影响游戏的进行。但在MySQL主从集群中,异步复制的机制可能导致数据丢失,所以需要特别注意。

在MySQL主从集群中,Master处理完事务后会将其写入binlog,并通知Dumpthread线程处理,然后完成事务的提交,这时Master就会给客户端返回一个成功的响应。但是,Master并不会关心binlog是否成功发送到任意一个Slave服务中,这一点就像在游戏中你发出操作指令后,即使服务器没有响应,你也可以继续玩游戏一样。而binlog会由一个dump线程异步发送给Slave服务,由于这个发送binlog的过程是异步的,所以在主服务向客户端反馈执行结果时,是不知道binlog是否同步成功了的。

举个例子来说,在游戏中你和你的朋友一起进行一次战斗,你的朋友被打败了并掉下了悬崖,但是服务器没有及时响应这个操作,导致你在自己的客户端上看到的是你的朋友还在继续战斗,而事实上他已经死亡了。在MySQL主从集群中,如果主服务宕机了,而从服务还没有备份到新执行的binlog,那就有可能会丢数据。

所以,在MySQL主从集群中需要特别注意异步复制的机制可能会导致数据丢失。如果需要保证数据的完整性和一致性,可以采用同步复制或者半同步复制的方式来进行数据同步。同步复制会阻塞Master服务,直到所有Slave服务都同步完成,而半同步复制则可以在Master服务写入binlog后,等待至少一个Slave服务同步完成后再进行事务提交。这样可以避免数据丢失的风险。

image

📝 半同步复制

半同步复制机制是一种介于异步复制和全同步复制之前的机制。这种复制机制能够有效提高数据的安全性,但也会造成一定的延迟和服务性能下降。下面我们来详细了解一下半同步复制机制的具体实现和应用。

先来说异步复制,这种复制机制是MySQL自带的默认机制,主库将事务写入binlog后,就会立即返回给客户端响应,不会等到从库应用这个事务。这种机制的好处是可以快速响应客户端的请求,但是数据的安全性不能得到保证,如果主库宕机,从库的数据可能是不完整的。

全同步复制机制相对于异步复制就更加保险,它会等待至少一个从库将binlog应用成功后才会返回响应。这样能够确保每个从库都能够完整复制主库的数据。但是,这种机制会造成主库响应速度变慢,从而影响整个系统的性能。

半同步复制机制则在这两种机制之间找到了一个平衡点。主库在执行完客户端提交的事务后,并不是立即返回客户端响应,而是等待至少一个从库接收并写到relay log中,才会返回给客户端。这种机制能够保证每个从库都能够收到主库的binlog,并且从库也能够将binlog应用成功。但是,半同步复制并不能保证数据的完整性,因为从库可能会出现崩溃等问题,从而无法成功应用主库的binlog,导致数据丢失。

半同步复制的实现需要基于特定的扩展模块,MySQL从5.5版本开始,往上的版本都默认自带了这个模块。这个模块包含在MySQL安装目录下的lib/plugin目录下的semisync_master.so和semisync_slave.so两个文件中。需要在主服务上安装semisync_master模块,在从服务上安装semisync_slave模块。

image

下面我们再来看一下半同步复制的一些配置参数:

  1. rpl_semi_sync_master_wait_point=AFTER_COMMIT:表示在主库等待ack的一个时间点,这个参数可以设置成AFTER_COMMIT或者AFTER_SYNC,AFTER_SYNC表示在主库等待ack的时候必须等到从库把binlog写入磁盘中才能返回响应。
  2. rpl_semi_sync_master_wait_for_slave_count=1:表示主库必须等待至少一个从库应用成功后才会返回响应。
  3. rpl_semi_sync_master_timeout=100:表示等待从库ack的超时时间,单位为毫秒。

通过上面这些参数的设置,我们可以灵活地调整半同步复制的性能和安全性。但是需要注意的是,半同步复制机制会造成一定程度的延迟,这个延迟时间最少是一个TCP/IP请求往返的时间。因此,在使用半同步复制机制时,需要仔细权衡安全性和性能,选择合适的配置参数。

总之,半同步复制机制是一种介于异步复制和全同步复制之间的机制,能够提高数据的安全性,同时也会造成一定的延迟和性能下降。在使用半同步复制机制时,需要灵活地调整配置参数,权衡安全性和性能,以达到最优化的效果。

📝 增强半同步复制

MySQL的半同步模式又有两种:普通半同步和增强半同步。它们的区别在哪里呢?下面,我将用通俗易懂的语言为大家讲解。

🔥普通半同步的问题

首先,我们来了解一下普通半同步的工作原理。在普通半同步模式下,当主库执行一条写操作(比如INSERT、UPDATE、DELETE等)时,会将这条写操作记录到二进制日志(Binlog)中,并立即返回一个“ACK”(表示确认)。从库会定期读取主库的Binlog,并将其中的写操作同步到自己的数据库中。当从库收到写操作并成功执行后,会向主库发送一个“ACK”表示确认。

但是,普通半同步模式也有一个问题。这个问题是由于等待ACK的点是在主库执行完写操作并提交(Commit)到磁盘后,才开始等待从库的ACK。这意味着在等待ACK期间,主库会立即将数据变更同步到客户端,并被用户看到。如果此时从库还未收到Binlog,且发生了主从切换,那么从库的数据就会出现延迟,用户会看到已经变更的数据,而从库中还是老数据,这就是著名的“延迟问题”。

🔥增强半同步的解决方法

那么,如何解决延迟问题呢?这就需要引入增强半同步模式。在增强半同步模式下,等待ACK的点被放在了主库提交数据到磁盘之前。也就是说,在主库执行完写操作后,它会将写操作记录到Binlog中,并立即向从库发送一个“ACK”表示确认,但这时数据还没有被提交到磁盘中,也就是外界无法看到数据变更。当从库收到写操作后,会立即将数据同步到自己的数据库中,并向主库发送一个“ACK”表示确认。当主库将写操作提交到磁盘后,它会等待从库的“ACK”确认。如果主库在一定的时间(rpl_semi_sync_master_wait_point)内没有收到从库的“ACK”,它就会认为从库故障,并将该从库从同步列表(Replication List)中剔除,从而避免了数据不一致的问题。

举个例子,假设你在主库上执行了一条INSERT语句插入一条新纪录。在普通半同步模式下,当INSERT语句执行成功后,你就可以在客户端看到这条新纪录了。但在增强半同步模式下,在主库插入新纪录后,它会向从库发送一个“ACK”表示确认,但数据并未提交到磁盘,因此外界无法看到这条新纪录。当从库收到新纪录并插入到自己的数据库中后,才会向主库发送一个“ACK”表示确认。当主库将数据提交到磁盘后,它才开始等待从库的“ACK”。如果在一定的时间内没有收到从库的“ACK”,主库就会认为从库故障,避免了数据不一致的问题。

综上所述,增强半同步模式通过将等待ACK的点放在主库提交数据到磁盘之前,避免了普通半同步模式下的延迟问题,从而提高了数据库的可靠性和可用性。虽然增强半同步模式会增加一些额外的网络通信开销,但对于重要的生产环境来说,确保数据的一致性和可靠性显然更为重要。

  • 32
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值