本系列文章:
Mysql学习之路(一)数据类型、关联查询、常用函数、事务、视图
Mysql学习之路(二)Mysql SQL语句练习题
Mysql学习之路(三)索引、存储引擎、锁
Mysql学习之路(四)Mysql架构、Mysql数据库优化
Mysql学习之路(五)SQL语句优化、慢查询、执行计划
一、Mysql架构
1.1 一条SQL查询语句是如何执行的
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异常重启了。两种解决方案:
定期断开长连接
。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后要查询再重连。- 如果用的是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版本直接将查询缓存的整块功能删掉了。
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字段没有索引,那么执行器的执行流程是这样的:
- 调用InnoDB引擎接口取这个表的第一行,判断ID值是不是10,如果不是则跳过,如果是则将这行存在结果集中;
- 调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
- 执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。
对于有索引的表,执行的逻辑也差不多。第一次调用的是“取满足条件的第一行”这个接口,之后循环取“满足条件的下一行”这个接口,这些接口都是引擎中已经定义好的。
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 还支持外键引用完整性约束。
特点:
支持事务,支持外键
,因此数据的完整性、一致性更高。支持行级别的锁和表级别的锁
。- 支持读写并发,写不阻塞读(MVCC)。
- 特殊的索引存放方式,可以减少 IO,提升查询效率。
如何选择存储引擎?
如果对数据一致性要求比较高,需要事务支持,可以选择 InnoDB
。如果数据查询多更新少,对查询性能要求比较高,可以选择 MyISAM
。- 如果需要一个用于查询的临时表,可以选择 Memory。
1.1.7 执行引擎,返回结果
1.2 MySQL 数据存储文件
每张 InnoDB 的表有两个文件(.frm 和.ibd),MyISAM 的表有三个文件(.frm、.MYD、.MYI)。
.frm 是 MySQL 里面表结构定义的文件
,不管你建表的时候选用任何一个存储引擎都会生成。
1.2.1 MyISAM
在 MyISAM 里面,另外有两个文件:一个是.MYD
文件,D代表Data,是MyISAM 的数据文件,存放数据记录
。一个是.MYI
文件,I代表Index,是MyISAM的索引文件,存放索引
。
MyISAM 的 B+Tree 里面,叶子节点存储的是数据文件对应的磁盘地址。所以从索引文件.MYI 中找到键值后,会到数据文件.MYD 中获取相应的数据记录。
1.2.2 InnoDB
在 InnoDB 里面,它是以主键为索引来组织数据的存储的,所以索引文件和数据文件是同一个文件,都在.ibd 文件里面
。在 InnoDB 的主键索引的叶子节点上,它直接存储了我们的数据。
二、Mysql日志
2.1 Redo日志和Undo日志
这两种日志是存储引擎层面的日志
。
- 1、Redo日志
Redo日志,是Innodb存储引擎的日志文件
。
当发生数据修改的时候,innodb引擎会先将记录写到redo log中,并更新内存,此时更新就算是完成了,同时innodb引擎会在合适的时机将记录操作到磁盘中。
Redolog是固定大小的,是循环写的过程。
有了redolog之后,innodb就可以保证即使数据库发生异常重启,之前的记录也不会丢失,叫做crash-safe。
Redo保证了持久性(隔离性是通过锁来实现)。
- 2、Undo日志
Undo Log是为了实现事务的原子性,在MySQL数据库InnoDB存储引擎中
,还用Undo Log来实现多版本并发控制。
在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为Undo Log)。然后进行数据的修改。如果出现了错误或者用户执行了ROLLBACK语句,系统可以利用Undo Log中的备份将数据恢复到事务开始之前的状态。
注意:undo log是逻辑日志,可以理解为:
当delete一条记录时,undo log中会记录一条对应的insert记录;
当insert一条记录时,undo log中会记录一条对应的delete记录;
当update一条记录时,它记录一条对应相反的update记录。
2.2 Binlog日志
Binlog是Server层面的日志
,主要做mysql功能层面的事情。
Binlog与redo日志的区别:
redo是innodb独有的
,binlog是所有引擎都可以使用的- redo是物理日志,记录的是在某个数据页上做了什么修改,binlog是逻辑日志,记录的是这个语句的原始逻辑
redo是循环写的,空间会用完
,binlog是可以追加写的,不会覆盖之前的日志信息
Binlog中会记录所有的逻辑,并且采用追加写的方式。一般在企业中数据库会有备份系统(用于应付数据丢失等情况),可以定期执行备份,备份的周期可以自己设置。
恢复数据的过程:
- 找到最近一次的全量备份数据
- 从备份的时间点开始,将备份的binlog取出来,重放到要恢复的那个时刻
2.3 数据更新的流程
执行流程:
- 执行器先从引擎中找到数据,如果在内存中直接返回,如果不在内存中,查询后返回;
- 执行器拿到数据之后会先修改数据,然后调用引擎接口重新写入数据;
- 引擎将数据更新到内存,同时写数据到redo中,此时处于prepare阶段,并通知执行器执行完成,随时可以操作;
- 执行器生成这个操作的Binlog;
- 执行器调用引擎的事务提交接口,引擎把刚刚写完的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,与原库的值不同。
可以看到,如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。
三、主从复制
- 为什么需要主从复制?
- 在业务复杂的系统中,有这么一个情景,有一句sql语句需要锁表,导致暂时不能使用读的服务,那么就很影响运行中的业务,
使用主从复制,让主库负责写,从库负责读,这样,即使主库出现了锁表的情景,通过读从库也可以保证业务的正常运作
。 - 做数据的热备。
- 架构的扩展。业务量越来越大,I/O访问频率过高,单机无法满足,此时做多库的存储,降低磁盘I/O访问的频率,提高单个机器的I/O性能。
MySQL主从复制是指数据可以从一个MySQL数据库服务器主节点复制到一个或多个从节点。
MySQL 默认采用异步复制方式
,这样从节点不用一直访问主服务器来更新自己的数据,数据的更新可以在远程连接上进行,从节点可以复制主数据库中的所有数据库或者特定的数据库,或者特定的表。
3.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将进入睡眠状态,等待下一次被唤醒
。
3.2 Mysql支持的复制类型
这个问题也可以换个说法:MySQL的binlog的格式。Binlog是记录所有数据库表结构变更(例如CREATE、ALTER TABLE…)以及表数据修改(INSERT、UPDATE、DELETE…)的二进制日志
。Binlog是Server层的日志。
MySQL的Binlog有三种格式:statment、row和mixed。
3.2.1 基于语句的复制(statment)
每一条会修改数据的sql都会记录在binlog中
。
在主服务器上执行的 SQL 语句,在从服务器上执行同样的语句。Mysql默认采用基于语句的复制,效率比较高。 一旦发现没法精确复制时,会自动选着基于行的复制。
- 优点
不需要记录每一行的变化,减少了Binlog日志量,节约了IO,提高性能。(相比row能节约多少性能与日志量,这个取决于应用的SQL情况,正常同一条记录修改或者插入row格式所产生的日志量还小于Statement产生的日志量,但是考虑到如果带条件的update操作,以及整表删除,alter表等操作,ROW格式会产生大量日志,因此在考虑是否使用ROW格式日志时应该跟据应用的实际情况,其所产生的日志量会增加多少,以及带来的IO性能问题。) - 缺点
由于记录的只是执行语句
,为了这些语句能在slave上正确运行,因此还必须记录每条语句在执行的时候的一些相关信息,以保证所有语句能在slave得到和在master端执行时候相同的结果。
3.2.2 基于行的复制(row)
不记录sql语句上下文相关信息,仅保存哪条记录被修改
。
把改变的内容复制过去,而不是把命令在从服务器上执行一遍. 从Mysql5.0 开始支持。
- 优点
: binlog中可以不记录执行的sql语句的上下文相关的信息,仅需要记录那一条记录被修改成什么了
。所以rowlevel的日志内容会非常清楚的记录下每一行数据修改的细节。而且不会出现某些特定情况下的存储过程,或function,以及trigger的调用和触发无法被正确复制的问题。 - 缺点
所有的执行的语句当记录到日志中的时候,都将以每行记录的修改来记录,这样可能会产生大量的日志内容
,比如一条update语句,修改多条记录,则binlog中每一条修改都会有记录,这样造成binlog日志量会很大,特别是当执行alter table之类的语句的时候,由于表结构修改,每条记录都发生改变,那么该表每一条记录都会记录到日志中。
3.2.3 混合类型的复制(mixed)
是以上两种方式的混合使用,一般的语句修改使用statment格式保存binlog,如一些函数,statement无法完成主从复制的操作,则采用row格式保存binlog
。
默认采用基于语句的复制,一旦发现基于语句的无法精确的复制时,就会采用基于行的复制。
3.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开销在容忍的范围内即可)。
3.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的复制线程的负担,转移到自己的身上。 这就是所谓的多级复制的概念。
3.4 Mysql主从形式
有以下五种形式:一主一从、主主复制、一主多从、多主一从、联级复制。
3.5 Mysql主从同步延时分析
mysql的主从复制都是单线程的操作
,主库对所有DDL和DML产生的日志写进binlog,由于binlog是顺序写,所以效率很高,slave的sql thread线程将主库的DDL和DML操作事件在slave中重放。DML和DDL的IO操作是随机的,不是顺序,所以成本要高很多,另一方面,由于sql thread也是单线程的,当主库的并发较高时,产生的DML数量超过slave的SQL thread所能处理的速度,或者当slave中有大型query语句产生了锁等待,那么延时就产生了。
解决方案:
- 业务的持久化层的实现采用分库架构,Mysql服务可平行扩展,分散压力。
- 单个库读写分离,一主多从,主写从读,分散压力。这样从库压力比主库高,保护主库。
- 服务的基础架构在业务和Mysql之间加入memcache或者redis的cache层。降低mysql的读压力。
- 不同业务的Mysql物理上放在不同机器,分散压力。
- 使用比主库更好的硬件设备作为slave,Mysql压力小,延迟自然会变小。
3.6 主从复制示例
两个虚拟机:ip:192.168.2.21(主) ip:192.168.2.22(从)。
3.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;
3.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
配置说明:
log-slave-updates:控制 slave 上的更新是否写入二进制日志,默认为0;若 slave 只作为从服务器,则不必启用;若 slave 作为其他服务器的 master,则需启用,启用时需和 log-bin、binlog-format 一起使用,这样 slave 从主库读取日志并重做,然后记录到自己的二进制日志中;
relay-log:设置中继日志文件基本名;
relay-log-index:设置中继日志索引文件名;
read-only:设置 slave 为只读,但具有super权限的用户仍然可写;
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
四、数据库优化
- 为什么要数据库优化?
- 系统的吞吐量瓶颈往往出现在数据库的访问速度上。
- 随着应用程序的运行,数据库的中的数据会越来越多,处理时间会相应变慢。
- 数据是存放在磁盘上的,读写速度无法和内存相比。
因此数据库的优化原则:减少系统瓶颈,减少资源占用,增加系统的反应速度。
4.1 数据库结构优化
数据库结构优化需要考虑数据冗余、查询和更新的速度、字段的数据类型是否合理等多方面的内容。
- 1、将字段很多的表分解成多个表
对于字段较多的表,如果有些字段的使用频率很低,可以将这些字段分离出来形成新表
。
因为当一个表的数据量很大时,会由于使用频率低的字段的存在而变慢。 - 2、增加中间表
对于需要经常联合查询的表,可以建立中间表以提高查询效率
。
通过建立中间表,将需要通过联合查询的数据插入到中间表中,然后将原来的联合查询改为对中间表的查询。 - 3、增加冗余字段
设计数据表时应尽量遵循范式理论的规约,尽可能的减少冗余字段,让数据库设计看起来精致、优雅。但是,合理的加入冗余字段可以提高查询速度。
冗余字段:在设计数据库时,某一字段属于一个表,但它又同时出现在另一个或多个表,且完全等同于它在其本来所属表的意义表示,那么这个字段就是一个冗余字段
。
冗余字段的值在一个表中修改了,就要想办法在其他表中更新,否则就会导致数据不一致的问题
。
冗余字段会导致一些问题,比如,冗余字段的值在一个表中被修改了,就要同步关联的表,否则会导致数据不一致。这要根据实际情况,平衡数据库性能,进行冗余字段的设计。 - 4、所有字段均定义为NOT NULL,除非真的需要存储null
- 5、提前做好数据量的预估,进行分表设计
不要等需要拆分时再拆,一般把表的数据量控制在千万级别。当单表数据量达到一定程度时(MySQL5.x时代的性能拐点则为1KW - 2KW行级别,具体需根据实际情况测试),为了提升性能,最为常用的方法就是分表。分表的策略可以是垂直拆分(比如:不同订单状态的订单拆分到不同的表),也可以是水平拆分(比如:按月将订单拆分到不同表)。如果在业务层分表,会将逻辑变得复杂,而且分散。可以引入分表的中间件屏蔽分表后的细节,让业务层像查询单表一样查询分表后的数据。比如Mycat。(访问量不大,但是表数据很多的表,我们可以采取分区表,实现起来也比较简单) - 6、合理的设置主键和索引
- 7、一般情况下都是采用业务主键
一般情况下都是采用业务主键,主键分自增主键和业务主键:
自增主键
写入、查询效率和磁盘利用率都高,但每次查询都需要两级索引,因为线上业务不会有直接使用主键列的查询。业务主键
写入、查询效率和磁盘利用率都低,但可以使用一级索引,依赖覆盖索引的特性,某些情况下在非主键索引上也可以实现1次索引完成查询
4.2 大表怎么优化
当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施:
- 1、限定数据的范围
务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,可以控制在一个月的范围内; - 2、读写分离
经典的数据库拆分方案,主库负责写,从库负责读; - 3、缓存
使用MySQL的缓存,另外对重量级、更新少的数据可以考虑使用应用级别的缓存; - 4、分库分表
主要有垂直分表和水平分表。
4.2.1 垂直分区
根据数据库里面数据表的相关性进行拆分。简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。垂直分表:把主键和一些列放在一个表,然后把主键和另外的列放在另一个表中
, 示例:
- 垂直拆分的优点
- 可以使得
行数据变小
,在查询时
减少读取的Block数,减少I/O次数
。- 垂直分区可以
简化表的结构,易于维护
。
- 垂直分表的适用场景
- 如果一个表中
某些列常用,另外一些列不常用
。- 可以使数据行变小,一个数据页能存储更多数据,查询时减少I/O次数。
- 垂直拆分的缺点
- 有些分表的策略基于应用层的逻辑算法,一旦逻辑算法改变,整个分表逻辑都会改变,扩展性较差。
- 对于应用层来说,逻辑算法增加开发成本。
管理冗余列,查询所有数据需要join操作
。- 垂直分区会
让事务变得更加复杂
。
4.2.2 水平分区
保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分可以支撑非常大的数据量。水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢
,这时可以把一张的表的数据拆成多张表来存放。示例:
- 水平拆分的优点
可以支持非常大的数据量
。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以 水平拆分最好分库 。- 应用端改造也少。
- 水平拆分的适用场景
表中的数据本身就有独立性
,例如表中分表记录各个地区的数据或者不同时期的数据,特别是有些数据常用,有些不常用。- 需要把数据存放在多个介质上。
- 水平切分的缺点
- 给应用增加复杂度,通常查询时需要多个表名,查询所有数据都需UNION操作。
- 在许多数据库应用中,这种复杂度会超过它带来的优点,查询时会增加读一个索引层的磁盘次数。
数据库分片的两种常见方案:
- 1、客户端代理
分片逻辑在应用端,封装在jar包中,通过修改或者封装JDBC层来实现。 当当网的 Sharding-JDBC 、阿里的TDDL是两种比较常用的实现。 - 2、中间件代理
在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。 阿里的 Mycat 、360的Atlas、网易的DDB等等都是这种架构的实现。
4.2.3 分库分表后面临的问题
- 1、事务支持
分库分表后,就成了分布式事务
了。如果依赖数据库本身的分布式事务管理功能去执行事务,将付出高昂的性能代价; 如果由应用程序去协助控制,形成程序逻辑上的事务,又会造成编程方面的负担。 - 2、跨库join
只要是进行切分,跨节点Join
的问题是不可避免的。但是良好的设计和切分却可以减少此类情况的发生。解决这一问题的普遍做法是分两次查询实现:在第一次查询的结果集中找出关联数据的id,根据这些id发起第二次请求得到关联数据
。 - 3、跨节点的count,order by,group by以及聚合函数问题
这些是一类问题,因为它们都需要基于全部数据集合进行计算。多数的代理都不会自动处理合并工作。解决方案:与解决跨节点join问题的类似,分别在各个节点上得到结果后在应用程序端进行合并
。和join不同的是每个结点的查询可以并行执行,因此很多时候它的速度要比单一大表快很多。但如果结果集很大,对应用程序内存的消耗是一个问题。 - 4、数据迁移,容量规划,扩容等问题
- 5、ID问题
一旦数据库被切分到多个物理结点上,我们将不能再依赖数据库自身的主键生成机制。一方面,某个分区数据库自生成的ID无法保证在全局上是唯一的;另一方面,应用程序在插入数据之前需要先获得ID,以便进行SQL路由。
4.3 分库分表之后,ID主键如何处理
因为要是分成多个表之后,每个表都是从 1 开始累加,这样是不对的,需要一个全局唯一的 id
来支持。
生成全局 id 有下面这几种方式:
- 1、UUID
不适合作为主键,因为太长了,并且无序不可读,查询效率低。比较适合用于生成唯一的名字的标示比如文件的名字。 - 2、数据库自增ID
两台数据库分别设置不同步长,生成不重复ID的策略来实现高可用。这种方式生成的 id 有序,但是需要独立部署数据库实例,成本高,还会有性能瓶颈
。 - 3、利用 Redis(RedisAtomicLong) 生成 ID
性能比较好,灵活方便,不依赖于数据库。但是,引入了新的组件造成系统更加复杂,可用性降低,编码更加复杂,增加了系统成本。
4.4 优化数据库的方法(概述)
- 选取最适用的字段属性,尽可能
减少定义字段宽度
,尽量把字段设置 NOT NULL
,例如’省份’、'性别’等固定值,最好使用ENUM。选取合适的存储引擎; - 使用连接(JOIN)来代替子查询,当然尽量避免JOIN查询;
- 适用联合(UNION)来代替手动创建的临时表;
- 事务处理;
- 锁定表、优化事务处理;
- 建立索引;
- 优化查询语句;
- 有外键约束会影响插入和删除性能,如果程序能够保证数据的完整性,那在设计数据库时就去掉外键;
表中允许适当冗余
,譬如,主题帖的回复数量和最后回复时间等;- UNION ALL 要比 UNION 快很多,所以,如果可以确认合并的两个结果集中不包含重复数据且不需要排序时的话,那么就使用 UNION ALL。
UNION 和 UNION ALL 关键字都是将两个结果集合并为一个,但这两者从使用和效率上来说都有所不同:
- 对重复结果的处理:UNION 在进行表连接后会筛选掉重复的记录,
Union All 不会去除重复记录
。- 对排序的处理:
Union 将会按照字段的顺序进行排序;UNION ALL 只是简单的将两个结果合并后就返回
。
- 找规律
分表
,减少单表中的数据量提高查询速度; - MySQL主从库
读写分离
。
4.5 Mysql数据库的优化(语句和参数)
说起Mysql优化,要了解一下Mysql原理,这样才能深入的理解那些sql规则。
MySQL客户端/服务端通信协议是“半双工”的:在任一时刻,要么是服务器向客户端发送数据,要么是客户端向服务器发送数据,这两个动作不能同时发生
。一旦一端开始发送消息,另一端要接收完整个消息才能响应它,所以我们无法也无须将一个消息切成小块独立发送,也没有办法进行流量控制。
客户端用一个单独的数据包将查询请求发送给服务器,所以当查询语句很长的时候,需要设置max_allowed_packet(server接受的数据包的大小)参数。当服务器响应客户端请求时,客户端必须完整的接收整个返回结果,而不能简单的只取前面几条结果,然后让服务器停止发送。因而在实际开发中,尽量保持查询简单且只返回必需的数据,减小通信间数据包的大小和数量是一个非常好的习惯,这也是查询中尽量避免使用SELECT *以及加上LIMIT限制的原因之一
。
4.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.description
FROM 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、避免导致索引失效
- 负向条件查询不能使用索引(not in/not exists都不是好习惯)
- 前导模糊查询不能使用索引(like’‘XX%’’)
- 数据区分度不大的字段不宜使用索引
- 在属性上进行计算不能命中索引
- 复合索引最左前缀不满足
- 6、强制类型转换会全表扫描
4.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表。
4.6 Mysql性能优化(架构)
说到性能调优,大部分时候想要实现的目标是让我们的查询更快。一个查询的动作又是由很多个环节组成的,每个环节都会消耗时间,要减少查询所消耗的时间,就要从每一个环节入手。
4.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 个连接就够了。这个公式从一定程度上来说对其他数据库也是适用的。
4.6.2 缓存——架构优化
在应用系统的并发数非常大的情况下,如果没有缓存,会造成两个问题:一方面是会给数据库带来很大的压力。另一方面,从应用的层面来说,操作数据的速度也会受到影响。可以用第三方的缓存服务来解决这个问题,例如 Redis。
- 1、主从复制
如果单台数据库服务满足不了访问需求,那我们可以做数据库的集群方案。这个时候需要用到复制技术(replication),被复制的节点称为 master,复制的节点称为 slave。slave 本身也可以作为其他节点的数据来源,这个叫做级联复制。
做了主从复制的方案之后,我们只把数据写入 master 节点,而读的请求可以分担到slave 节点。我们把这种方案叫做读写分离。
读写分离可以一定程度低减轻数据库服务器的访问压力,但是需要注意主从数据一致性的问题。
单线程
在早期的 MySQL 中,slave 的 SQL 线程是单线程。master 可以支持 SQL 语句的并行执行,配置了多少的最大连接数就是最多同时多少个 SQL 并行执行。而 slave 的 SQL 却只能单线程排队执行,在主库并发量很大的情况下,同步数据肯定会出现延迟。异步与全同步
在主从复制的过程中,MySQL 默认是异步复制的。也就是说,对于主节点来说,写入 binlog,事务结束,就返回给客户端了。对于 slave 来说,接收到 binlog,就完事儿了,master 不关心 slave 的数据有没有写入成功。
等待全部从库的事务执行完毕,才返回给客户端,这样的方式叫做全同步复制。从库写完数据,主库才返会给客户端。但这种方式事务执行的时间会变长,它会导致 master 节点性能下降。半同步复制
半同步复制:主库在执行完客户端提交的事务后不是立刻返回给客户端,而是等待至少一个从库接收到 binlog 并写到 relay log 中才返回给客户端。master 不会等待很长的时间,但是返回给客户端的时候,数据就即将写入成功了,因为它只剩最后一步了:就是读取 relay log,写入从库。
半同步复制提高了数据的安全性,同时它也造成了一定程度的延迟,它需要等待一个 slave 写入中继日志,这里多了一个网络交互的过程,所以,半同步复制最好在低延时的网络中使用。
这个是从主库和从库连接的角度,来保证 slave 数据的写入。
多库并行复制
MySQL 5.6版本里面支持了多库并行复制,即在不同的数据库执行不同的语句:
在大部分的情况下,我们都是单库多表的情况,在一个数据库里面怎么实现并行复制呢?很多时候他们本身就是互相不干扰的,比如这些事务是操作不同的表,或者操作不同的行,不存在资源的竞争和数据的干扰。异步复制之 GTID 复制
可以把那些在主库上并行执行的事务,分为一个组,并且给他们编号,这一个组的事务在从库上面也可以并行执行。这个编号,我们把它叫做 GTID,这种主从复制的方式,我们把它叫做基于 GTID 的复制。
要使用 GTID 复制,我们可以通过修改配置参数打开它,默认是关闭的,查看命令:
show global variables like 'gtid_mode';
无论是优化 master 和 slave 的连接方式,还是让从库可以并行执行 SQL,都是从数据库的层面去解决主从复制延迟的问题。除了数据库本身的层面之外,在应用层面,我们也有一些减少主从同步延迟的方法。
如果单个 master 节点或者单张表存储的数据过大的时候,单表的查询性能还是会下降,我们要进一步对单台数据库节点的数据分型拆分,这个就是分库分表。
- 2、分库分表
垂直分库,减少并发压力。水平分表,解决存储瓶颈。
垂直分库的做法,把一个数据库按照业务拆分成不同的数据库。
水平分库分表的做法,把单张表的数据按照一定的规则分布到多个数据库。
通过主从或者分库分表可以减少单个数据库节点的访问压力和存储压力,达到提升数据库性能的目的,但是如果 master 节点挂了,怎么办?此时就要用集群方案了。
4.7 MySQL数据库cpu飙升到500%的话怎么处理
当cpu飙升到 500%时,先用操作系统命令 top
命令(实时显示系统中各个进程的资源占用状况)观察是不是 mysqld 占用导致的,如果不是,找出占用高的进程,并进行相关处理。
如果是 mysqld 造成的, show processlist
(显示用户正在运行的线程),看看里面跑的 session 情况,是不是有消耗资源的 sql 在运行。找出消耗高的 sql,看看执行计划是否准确, index 是否缺失,或者实在是数据量太大造成。
一般来说,肯定要 kill 掉这些线程(同时观察 cpu 使用率是否下降),等进行相应的调整(比如说加索引、改 sql、改内存参数)之后,再重新跑这些 SQL
。
也有可能是每个 sql 消耗资源并不多,但是突然之间,有大量的 session 连进来导致 cpu 飙升,这种情况就需要跟应用一起来分析为何连接数会激增,再做出相应的调整,比如说限制连接数等。
4.8 表中有大字段X(例如:text 类型),且字段X不会经常更新,以读为主,将该字段拆成子表好处是什么
如果字段里面有大字段(text,blob)类型的,而且这些字段的访问并不多,这时候放在一起就变成缺点了。 MYSQL 数据库的记录存储是按行存储的,数据块大小又是固定的(16K),每条记录越小,相同的块存储的记录就越多。此时应该把大字段拆走,这样应付大部分小字段的查询时,就能提高效率。
当需要查询大字段时,此时的关联查询是不可避免的,但也是值得的。拆分开后,对字段的 UPDAE 就要 UPDATE 多个表了。