gitbook/Java性能调优实战/docs/120160.md

169 lines
14 KiB
Markdown
Raw Permalink Normal View History

2022-09-03 22:05:03 +08:00
# 39 | 数据库参数设置优化,失之毫厘差之千里
你好,我是刘超。
MySQL是一个灵活性比较强的数据库系统提供了很多可配置参数便于我们根据应用和服务器硬件来做定制化数据库服务。如果现在让你回想你可能觉得在开发的过程中很少去调整MySQL的配置参数但我今天想说的是我们很有必要去深入了解它们。
我们知道数据库主要是用来存取数据的而存取数据涉及到了磁盘I/O的读写操作所以数据库系统主要的性能瓶颈就是I/O读写的瓶颈了。MySQL数据库为了减少磁盘I/O的读写操作应用了大量内存管理来优化数据库操作包括内存优化查询、排序以及写入操作。
也许你会想我们把内存设置得越大越好数据刷新到磁盘越快越好不就对了吗其实不然内存设置过大同样会带来新的问题。例如InnoDB中的数据和索引缓存如果设置过大就会引发SWAP页交换。还有数据写入到磁盘也不是越快越好我们期望的是在高并发时数据能均匀地写入到磁盘中从而避免I/O性能瓶颈。
> SWAP页交换SWAP分区在系统的物理内存不够用的时候就会把物理内存中的一部分空间释放出来以供当前运行的程序使用。被释放的空间可能来自一些很长时间没有什么操作的程序这些被释放的空间的数据被临时保存到SWAP分区中等到那些程序要运行时再从SWAP分区中恢复保存的数据到内存中。
所以这些参数的设置跟我们的应用服务特性以及服务器硬件有很大的关系。MySQL是一个高定制化的数据库我们可以根据需求来调整参数定制性能最优的数据库。
不过想要了解这些参数的具体作用,我们先得了解数据库的结构以及不同存储引擎的工作原理。
## MySQL体系结构
我们一般可以将MySQL的结构分为四层最上层为客户端连接器主要包括了数据库连接、授权认证、安全管理等该层引用了线程池为接入的连接请求提高线程处理效率。
第二层是Server层主要实现SQL的一些基础功能包括SQL解析、优化、执行以及缓存等其中与我们这一讲主要相关的就是缓存。
第三层包括了各种存储引擎主要负责数据的存取这一层涉及到的Buffer缓存也和这一讲密切相关。
最下面一层是数据存储层,主要负责将数据存储在文件系统中,并完成与存储引擎的交互。
![](https://static001.geekbang.org/resource/image/52/a5/5297b9556d527dec788b5298d4810fa5.jpg)
接下来我们再来了解下当数据接收到一个SQL语句时是如何处理的。
### 1\. 查询语句
一个应用服务需要通过第一层的连接和授权认证再将SQL请求发送至SQL接口。SQL接口接收到请求之后会先检查查询SQL是否命中Cache缓存中的数据如果命中则直接返回缓存中的结果否则需要进入解析器。
解析器主要对SQL进行语法以及词法分析之后便会进入到优化器中优化器会生成多种执行计划方案并选择最优方案执行。
确定了最优执行计划方案之后执行器会检查连接用户是否有该表的执行权限有则查看Buffer中是否存在该缓存存在则获取锁查询表数据否则重新打开表文件通过接口调用相应的存储引擎处理这时存储引擎就会进入到存储文件系统中获取相应的数据并返回结果集。
### 2\. 更新语句
数据库更新SQL的执行流程其实跟查询SQL差不多只不过执行更新操作的时候多了记录日志的步骤。在执行更新操作时MySQL会将操作的日志记录到 binlog归档日志这个步骤所有的存储引擎都有。而InnoDB除了要记录 binlog 之外,还需要多记录一个 redo log重做日志
redo log 主要是为了解决 crash-safe 问题而引入的。我们知道当数据库在存储数据时发生异常重启我们需要保证存储的数据要么存储成功要么存储失败也就是不会出现数据丢失的情况这就是crash-safe了。
我们在执行更新操作时首先会查询相关的数据之后通过执行器执行更新操作并将执行结果写入到内存中同时记录更新操作到redo log的缓存中此时redo log中的记录状态为prepare并通知执行器更新完成随时可以提交事务。执行器收到通知后会执行binlog的写入操作此时的binlog是记录在缓存中的写入成功后会调用引擎的提交事务接口更新记录状态为commit。之后内存中的redo log以及binlog都会刷新到磁盘文件中。
## 内存调优
基于以上两个SQL执行过程我们可以发现在执行查询SQL语句时会涉及到两个缓存。第一个缓存是刚进来时的Query Cache它缓存的是SQL语句和对应的结果集。这里的缓存是以查询SQL的Hash值为key返回结果集为value的键值对判断一条SQL是否命中缓存是通过匹配查询SQL的Hash值来实现的。
很明显Query Cache可以优化查询SQL语句减少大量工作特别是减少了I/O读取操作。我们可以通过以下几个主要的设置参数来优化查询操作
![](https://static001.geekbang.org/resource/image/db/dd/db97c34b74f0903673badc256ba46cdd.jpg)
我们可以通过设置合适的 query\_cache\_min\_res\_unit 来减少碎片,这个参数最合适的大小和应用程序查询结果的平均大小直接相关,可以通过以下公式计算所得:
query\_cache\_size - Qcache\_free\_memory/ Qcache\_queries\_in\_cache
Qcache\_free\_memory 和 Qcache\_queries\_in\_cache 的值可以通过以下命令查询:
```
show status like 'Qcache%'
```
Query Cache虽然可以优化查询操作但也仅限于不常修改的数据如果一张表数据经常进行新增、更新和删除操作则会造成Query Cache的失效率非常高从而导致频繁地清除Cache中的数据给系统增加额外的性能开销。
这也会导致缓存命中率非常低,我们可以通过以上查询状态的命令查看 Qcache\_hits该值表示缓存命中率。如果缓存命中率特别低的话我们还可以通过query\_cache\_size = 0或者query\_cache\_type来关闭查询缓存。
经过了Query Cache缓存之后还会使用到存储引擎中的Buffer缓存。不同的存储引擎使用的Buffer也是不一样的。这里我们主要讲解两种常用的存储引擎。
### 1\. MyISAM存储引擎参数设置调优
MyISAM存储引擎使用key buffer缓存索引块MyISAM表的数据块则没有缓存它是直接存储在磁盘文件中的。
我们可以通过key\_buffer\_size设置key buffer缓存的大小而它的大小并不是越大越好。正如我前面所讲的key buffer缓存设置过大实际应用却不大的话就容易造成内存浪费而且系统也容易发生SWAP页交换一般我是建议将服务器内存中可用内存的1/4分配给key buffer。
如果要更准确地评估key buffer的设置是否合理我们还可以通过缓存使用率公式来计算
1-((key\_blocks\_unused\*key\_cache\_block\_size)/key\_buffer\_size)
> key\_blocks\_unused表示未使用的缓存簇blocks
> key\_cache\_block\_size表示key\_buffer\_size被分割的区域大小key\_blocks\_unused\*key\_cache\_block\_size则表示剩余的可用缓存空间一般来说缓存使用率在80%作用比较合适)。
### 2\. InnoDB存储引擎参数设置调优
InnoDB Buffer Pool简称IBP是InnoDB存储引擎的一个缓冲池与MyISAM存储引擎使用key buffer缓存不同它不仅存储了表索引块还存储了表数据。查询数据时IBP允许快速返回频繁访问的数据而无需访问磁盘文件。InnoDB表空间缓存越多MySQL访问物理磁盘的频率就越低这表示查询响应时间更快系统的整体性能也有所提高。
我们一般可以通过多个设置参数来调整IBP优化InnoDB表性能。
* **innodb\_buffer\_pool\_size**
IBP默认的内存大小是128M我们可以通过参数innodb\_buffer\_pool\_size来设置IBP的大小IBP设置得越大InnoDB表性能就越好。但是将IBP大小设置得过大也不好可能会导致系统发生SWAP页交换。所以我们需要在IBP大小和其它系统服务所需内存大小之间取得平衡。MySQL推荐配置IBP的大小为服务器物理内存的80%。
我们也可以通过计算InnoDB缓冲池的命中率来调整IBP大小
(1-innodb\_buffer\_pool\_reads/innodb\_buffer\_pool\_read\_request)\*100
但如果我们将IBP的大小设置为物理内存的80%以后发现命中率还是很低此时我们就应该考虑扩充内存来增加IBP的大小。
* **innodb\_buffer\_pool\_instances**
InnoDB中的IBP缓冲池被划分为了多个实例对于具有数千兆字节的缓冲池的系统来说将缓冲池划分为单独的实例可以减少不同线程读取和写入缓存页面时的争用从而提高系统的并发性。该参数项仅在将innodb\_buffer\_pool\_size设置为1GB或更大时才会生效。
在windows 32位操作系统中如果innodb\_buffer\_pool\_size的大小超过1.3GBinnodb\_buffer\_pool\_instances默认大小就为innodb\_buffer\_pool\_size/128MB否则默认为1。
而在其它操作系统中如果innodb\_buffer\_pool\_size大小超过1GBinnodb\_buffer\_pool\_instances值就默认为8否则默认为1。
为了获取最佳效率建议指定innodb\_buffer\_pool\_instances的大小并保证每个缓冲池实例至少有1GB内存。通常建议innodb\_buffer\_pool\_instances的大小不超过innodb\_read\_io\_threads + innodb\_write\_io\_threads之和建议实例和线程数量比例为1:1。
* **innodb\_read\_io\_threads** / **innodb\_write\_io\_threads**
在默认情况下MySQL后台线程包括了主线程、IO线程、锁线程以及监控线程等其中读写线程属于IO线程主要负责数据库的读取和写入操作这些线程分别读取和写入innodb\_buffer\_pool\_instances创建的各个内存页面。MySQL支持配置多个读写线程即通过innodb\_read\_io\_threads和innodb\_write\_io\_threads设置读写线程数量。
读写线程数量值默认为4也就是总共有8个线程同时在后台运行。innodb\_read\_io\_threads和innodb\_write\_io\_threads设置的读写线程数量与innodb\_buffer\_pool\_instances的大小有关两者的协同优化是提高系统性能的一个关键因素。
在一些内存以及CPU内核超大型的数据库服务器上我们可以在保证足够大的IBP内存的前提下通过以下公式协同增加缓存实例数量以及读写线程。
( innodb\_read\_io\_threads + innodb\_write\_io\_threads ) = innodb\_buffe\_pool\_instances
如果我们仅仅是将读写线程根据缓存实例数量对半来分即读线程和写线程各为实例大小的一半肯定是不合理的。例如我们的应用服务读取数据库的数据多于写入数据库的数据那么增加写入线程反而没有优化效果。我们一般可以通过MySQL服务器保存的全局统计信息来确定系统的读取和写入比率。
我们可以通过以下查询来确定读写比率:
```
SHOW GLOBAL STATUS LIKE 'Com_select';//读取数量
SHOW GLOBAL STATUS WHERE Variable_name IN ('Com_insert', 'Com_update', 'Com_replace', 'Com_delete');//写入数量
```
如果读大于写,我们应该考虑将读线程的数量设置得大一些,写线程数量小一些;否则,反之。
* **innodb\_log\_file\_size**
除了以上InnoDB缓存等因素之外InnoDB的日志缓存大小、日志文件大小以及日志文件持久化到磁盘的策略都影响着InnnoDB的性能。 InnoDB中有一个redo log文件InnoDB用它来存储服务器处理的每个写请求的重做活动。执行的每个写入查询都会在日志文件中获得重做条目以便在发生崩溃时可以恢复更改。
当日志文件大小已经超过我们参数设置的日志文件大小时InnoDB会自动切换到另外一个日志文件由于重做日志是一个循环使用的环在切换时就需要将新的日志文件脏页的缓存数据刷新到磁盘中触发检查点
理论上来说innodb\_log\_file\_size设置得越大缓冲池中需要的检查点刷新活动就越少从而节省磁盘I/O。那是不是将这个日志文件设置得越大越好呢如果日志文件设置得太大恢复时间就会变长这样不便于DBA管理。在大多数情况下我们将日志文件大小设置为1GB就足够了。
* **innodb\_log\_buffer\_size**
这个参数决定了InnoDB重做日志缓冲池的大小默认值为8MB。如果高并发中存在大量的事务该值设置得太小就会增加写入磁盘的I/O操作。我们可以通过增大该参数来减少写入磁盘操作从而提高并发时的事务性能。
* **innodb\_flush\_log\_at\_trx\_commit**
这个参数可以控制重做日志从缓存写入文件刷新到磁盘中的策略默认值为1。
当设置该参数为0时InnoDB每秒种就会触发一次缓存日志写入到文件中并刷新到磁盘的操作这有可能在数据库崩溃后丢失1s的数据。
当设置该参数为 1 时,则表示每次事务的 redo log 都会直接持久化到磁盘中,这样可以保证 MySQL 异常重启之后数据不会丢失。
当设置该参数为 2 时,每次事务的 redo log 都会直接写入到文件中,再将文件刷新到磁盘。
在一些对数据安全性要求比较高的场景中显然该值需要设置为1而在一些可以容忍数据库崩溃时丢失1s数据的场景中我们可以将该值设置为0或2这样可以明显地减少日志同步到磁盘的I/O操作。
## 总结
MySQL数据库的参数设置非常多今天我们仅仅是了解了与内存优化相关的参数设置。除了这些参数设置我们还有一些常用的提高MySQL并发的相关参数设置总结如下
![](https://static001.geekbang.org/resource/image/be/4c/be83083a261bf1302aca81c122b0ac4c.jpg)
## 思考题
我们知道InnoDB的IBP的内存大小是有限的你知道InnoDB是如何将热点数据留在内存中淘汰非热点数据的吗
期待在留言区看到你的答案。也欢迎你点击“请朋友读”,把今天的内容分享给身边的朋友,邀请他一起讨论。