SQL优化--优化MySQL Server

MySQL实例由一组后台线程、一些内存块和若干服务线程组成

在默认情况下 MySQL有7组后台线程 分别是1主线程 4组IO线程 1个锁线程 1个错误监控线程 MySQL5.5之后增加了purge线程 

master thread:主要负责将脏缓存页刷新到数据文件 执行purge操作 触发检查点 合并插入缓冲区等

insert buffer thread:主要负责插入缓冲区的合并操作

read thread:负责数据库读取操作 可配置多个读线程

write thread:负责写操作 可配置多个写线程

log thread:用于将重做日志刷新到logfile中

purge thread:MySQL5.5之后用单独的purge thread执行purge操作

lock thread:负责锁控制和死锁检测

错误监控线程:主要负责错误监控和错误处理

show engine innodb status\G;

--内存优化原则

1.将尽量多的内存分配给MySQL做缓存 ,但要给操作系统和其他程序的运行预留足够的内存 否则产生SWAP页交换 将影响系统性能

2.MyISAM的数据文件读取依赖于OS自身的IO缓存 因此 如果有MyISAM表 就要预留更多的内存给OS做IO缓存

3.排序区、连接区等缓存是分配给每个数据库session专用的 默认值的设置要根据最大连接数合理分配 如果设置太大 不但浪费内存资源 而且在并发连接较高时会导致物理内存耗尽

--MyISAM内存优化

MyISAM引擎使用key_buffer缓存索引块,以加速MyISAM索引的读写速度

对于MyISAM表的数据块 MySQL没有特别的缓存机制 完全依赖于OS的IO缓存

1.key_buffer_size设置

其决定MyISAM索引块缓存区大小 直接影响MyISAM表的存取效率。可以在MySQL参数文件中设置该值 对于一般MyISAM数据库 建议至少将1/4可用内存分配给key_buffer_size

通过检查key_read_requests key_reads key_write_requests 和 key_writes等MySQL状态变量来评估索引缓存的效率

一般来说 索引块物理读比例 key_reads / key_read_requests < 0.01 索引块写比例也要小 key_writes / key_write_requests 对于更新和删除操作特别多的应用 key_writes / key_write_requests可能接近1 而对于每次更新很多行记录的应用 key_writes / key_write_requests会比较小

key_buffer使用率计算公式:

1-((key_blocks_unused*key_cache_block_size)/key_buffer_size)

一般来说 使用率在80%左右比较合适 大于80%可能因索引缓存不足而导致性能下降  小于80%会导致内存浪费

2.使用多个索引缓存

MySQL通过各session共享的key buffer提高了MyISAM索引存取的性能 不能消除session间对key buffer的竞争  

为了减少session间对key buffer的竞争 MySQL从5.1开始引入多索引缓存机制 从而可以将不同表的索引缓存到不同的key buffer中去。

可以通过下述命令创建(删除)新的key buffer

set global hot_cache.key_buffer_size=128*1024;

set global hot_cache.key_buffer_size=0;

--不能删除默认的key buffer:

show variables like 'key_buffer_size';

set global key_buffer_size = 0;

默认情况下 MySQL将使用默认key buffer缓存MyISAM表的索引 我们可以用cache index命令指定表的索引缓存:

cache index sales,sales2 in hot_cache;

常见做好通过配置文件在mysql启动时自动创建并加载索引缓存:

key_buffer_size = 4G

hot_cache.key_buffer_size = 2G

cold_cache.key_buffer_size = 1G

init_file=/path/to/data-directory/mysql_init.sql

在mysql_init.sql中,可以通过cache index命令分配索引缓存 并用load index into cache命令来进行索引预加载

cache index sales in hot_cache;

cache index sales2 in cold_cache;

load index into cache sales,sales2;

3.调整“中点插入策略”

默认 MySQL使用LRU策略选择要淘汰的索引数据块 这种算法不是很精细 在某些情况下会导致真正的热块被淘汰。

若出现这种情况 除了使用上面介绍的多个索引缓存机制 同时可以利用中点插入策略优化索引块淘汰算法。该策略是对LRU算法的改进 将LRU链分成两部分:hot子表和warm子表 当一个索引块读入内存时 先被放到LRU链表重点 (warm子表尾部)

--达到一定命中次数后 索引块会被晋升到hot子表的尾部--索引块在hot子表流转 --到达hot子表头部并超过一定时间 将由hot子表头部降级到warm子表的头部 --需要淘汰索引块 缓存管理程序会选择优先warm表头部的内存块。能够避免偶尔被访问的索引块将访问频繁的热块淘汰。

调节key_cache_division_limit控制多大比例的缓存用做warm子表 ,key_cache_division_limit默认值100(所有缓存块都放在warm子表 不启用中点插入策略)

将大致30%的缓存用来cache最热的索引块 设置:

set global key_cache_division_limit =70;

set global hot_cache.key_cache_division_limit=70;

4.调整read_buffer_size和read_rnd_buffer_size

如果需要经常顺序扫描MyISAM表 可以增大read_buffer_size的值来改善性能 需要注意的是:read_buffer_size是每个session独占的 默认值太大 会造成内存浪费 甚至物理内存耗尽。

对应需要做排序的MyISAM表查询 带有order by子句的SQL 适当增大read_rnd_buffer_size的值 可以改善此类SQL的性能 同样要注意的是:read_rnd_buffer_size也是按照session分配 默认值不能设置太大

--InnoDB内存优化

1.InnoDB缓存机制

InnoDB用一块内存区做IO缓存池 该缓存池不仅用来缓存InnoDB的索引块 而且用来缓存InnoDB的数据库

内部 InnoDB缓存池逻辑上由free list(空闲缓存块列表) 、flush list(需要刷新到磁盘的缓存块列表)和LRU list(InnoDB正在使用的缓存块 InnoDB缓存池的核心)组成

LRU算法原理:将LRU list分为young sublist和old sublist,数据从磁盘读入时 数据从磁盘读入时 会将该缓存块插入到LRU list的中点(old sublist头部) 经过一段时间访问(innodb_old_blocks_time系统参数决定)该数据块将会由old sublist转移到young sublist头部 也就是整个LRU list的头部 随着时间的推移 young sublist和old  sublist中较少被访问的缓存块将从各自链表的头部逐渐向尾部 需要淘汰数据块 优先从链表尾部淘汰 。 这种设计同样为了防止偶尔被访问的索引块将访问频繁的热块淘汰。

脏页的刷新存在于flush list和LRU list这两个链表 LRU上也存在可以刷新的脏页 这里直接可以刷新的

通过调整InnoDB buffer pool的大小 改变young sublist和old sublist的分配比例、控制藏缓存的刷新活动  使用多个InnoDB缓存方法来优化InnoDB的性能

2.innodb_buffer_pool_size设置

该设置决定InnoDB存储引擎表数据和索引数据的最大缓存区大小 和MyISAM存储引擎不同 InnoDB buffer pool同时为数据块和索引块提供数据缓存 保证OS等其他内存可用情况下 值越大 性能越高 访问InnoDB表需要的磁盘I/O越少 性能就越高。 将80%内存分给buffer pool,一定要注意避免设置过大而导致页交换。

以下命令查看buffer pool使用情况:

[root@mysqldb 3306]# mysqladmin -uroot -p -S /mysql/data/3306/mysql.sock ext|grep -i innodb_buffer_pool

3.调整old sublist大小

在LRU list中 old sublist的比例由系统参数innodb_old_blocks_pct决定 取值范围5~95 默认37

mysql> show global variables like '%innodb_old_blocks%';

若young/s的值很低 可能需要适当增大innodb_old_blocks_time的值

4.调整innodb_old_blocks_time的设置

innodb_old_blocks_time决定了缓存数据块由old sublist转移到Young sublist的快慢 当一个缓存数据块被插入到midpoint(old sublist)后 至少要在old sublist停留超过innodb_old_block才被转移到new sublist。innodb_old_blocks_time设置为1000 当出现table scan时 InnoDB先将数据块载入到midpoint 上 程序读取数据块 数据块在old sublist中停留时间不到innodb_old_blocks_time 所以不会被转移到有young sublist中 避免了表扫描污染buffer pool的情况。

根据InnoDB Monitor的输出信息来调整innodb_old_blocks_time的值 如果young/s很高 考虑innodb_old_blocks_time适当调大 防止表扫描将真正的热数据淘汰 可以动态设置。

5.调整缓存池数量,减少内部对缓存池数据结构的争用

MySQL内部不同线程对InnoDB缓存池的访存在互斥(某些阶段) 内部竞争会产生性能问题 在高并发和buffer pool较大情况下 解决这个问题 InnoDB的缓存系统引入了innodb_buffer_instances配置参数 较大缓存池 适当增大次参数值 降低并发导致的内部缓存访问冲突 改善性能。InnoDB缓存系统会将参数innodb_buffer_pool_size指定大小缓存平分为innodb_buffer_pool_instances个buffer pool。

6.控制innodb buffer刷新 延长数据缓存时间 减缓磁盘I/O

在InnoDB找不到干净可用缓存或检查点被触发等情况下 InnoDB的后台线程会开始把“脏的缓存页”回写到磁盘文件中 缓存刷新  希望buffer pool的数据在缓存中停留时间尽可能长 以备重用 减少磁盘的IO次数

InnoDB buffer pool刷新快慢取决于两个参数

innodb_max_dirty_pages_pct:代表磁盘系统的IO能力 一定程度上代表磁盘每秒可完成IO次数 innodb_io_capacity默认值为200 对于转速较低磁盘 可将Innodb_io_capacity的值降低到100 对于固态硬盘和多个磁盘组成的盘 innodb_io_capacity值可以适当增大

innodb_io_capacity决定一批刷新脏页的数量 当缓存池脏页的比例达到Innodb_max_dirty_pages_pct InnoDB大约将innodb_io_capacity个已改变的缓存页刷新到磁盘;当脏页比例小于innodb_max_dirty_pages_pct时 如果innodb_daaptive_flushing的设置为true InnoDB根据函数buf_flush_get_desired_flush_rate返回的重做

7.InnoDB doublewrite

脏页刷新 使用双写策略  原因:MySQL数据页大小与OS的数据页大小(4)不一致 无法保证InnoDB缓存页被完整 一致刷新到磁盘 而InnoDB的redo日志记录数据页改变部分 未记录完成前像 当发生部分写或断裂写时 就会出现无法恢复的问题 

实现原理:用系统表空间中的一块连续磁盘空间(100个连续数据页 大小2MB) 进行脏页刷新时 首先将脏页的副本被真正写入磁盘 最后InnoDB后台IO线程将脏页刷新到磁盘数据文件 

做恢复发现不一致的页  InnoDB会用系统表空间doublewrite buffer区相应副本恢复数据页

默认情况下 开启的

show global variables like '%doublewrite%';

对于高性能 又能容忍几段情况下少量数据丢失应用 通过在配置文件中增加innodb_doublewrite=0参数设置来关闭doublewrite 尽量满足性能方面的要求

调整用户服务线程排序缓存区

通过show global status 看到sort_merge_passes值很大 可以考虑通过调整参数sort_buffer_size值增大排序缓存区 改善带有order by子句或group 子句sql性能 对于无法通过索引进行连接操作的查询 可以通过增大join_buffer_size来改善性能。

sort buffer和join buffer都是面向客户服务线程分配 设置过大会造成内存浪费 尤其join buffer 如果是多表关联复杂查询 可能会分配多个Join buffer 策略是设置较小的全局join_buffer_size 对需要做复杂连接操作的session 设置较大Join_buffer_ size

InnoDB   机制及优化

更新数据 内部操作流程:

1)将数据写入innodb buffer pool 并对相关记录加独占锁

2)将UNDO信息写入undo表空间的回滚段中

3)更改缓存页中的数据 并将更新记录写入到redo buffer中

4)提交时 根据InnoDB_flush_log_at_trx_commit设置 用不同方式将log buffer中的更新记录刷新到InnoDB redo log file 然后释放独占锁

5)最后 后台IO线程根据需要将缓存中更新数据刷新到磁盘文件中

LSN成为日志序列号 实际上对应日志文件的偏移量 

生成公式: 新的LSN=旧的LSN+写入的日志大小

例子:日志大小600M 目前LSN=1G 现在要将256字节写入redo log 实际写入过程:

求出偏移量:LSN>>redolog 取余 偏移量400M

写入日志:找到400M位置 将256字节写入 下一个LSN=100000256

影响InnoDB的性能参数:InnoDB buffer pool/InnoDB log buffer / redo日志大小/innodb_flush_log_at_trx_commit参数的设置等

innodb_flush_log_at_trx_commit

:控制将redo buffer中更新记录写入到日志文件以及将日志文件数据刷新到磁盘的操作实际。调整这个参数 可以在性能和数据安全之间做取舍

设置为0 事务提交 InnoDB不会立即出发将缓存日志写到磁盘文件的操作 而是每秒触发一次缓存日志写入磁盘 并调用操作系统fsync刷新IO缓存

设置为1 在每个事务提交 InnoDB立即将缓存中的redo日志写回到日志文件 调用操作系统fsync刷新到IO缓存

设置为2 每个事务提交 InnoDB立即将缓存中的redo日志写到logfile中 并不马上调用fsync来刷新IO缓存 每秒只做一次磁盘IO缓存刷新操作

innodb_flush_log_at_trx_commit默认参数为1 每个事务提交时 都能从Log buffer写更新记录到日志文件 实际刷新磁盘缓存,该操作能满足事务的持久化要求 最安全。

允许DB崩溃丢失少部分数据 提高性能 设置0或2 减少日志同步IO 加快事务提交 改善性能

设为0 DB崩溃 最后1s的事务重做日志可能会由于来不及写入磁盘而丢失 最不安全 最高效

设为2 DB崩溃 已执行重做日志写入磁盘操作 只是没有做磁盘IO刷新操作 折中 在性能和安全之间

log file size

控制检查点:一个日志文件写满 会自动切换到另一个日志文件 切换时会触发checkpoint 导致InnoDB缓存脏页的小批量刷新 明显降低InnoDB的性能  一般 平均每半小时写满1个日志文件比较合适 通过计算每小时产生的日志量并估算合适的innodb_log_file_size的值。首先 计算InnoDB每分钟产生量

--

select @a1 := variable_value AS a1

FROM information_schema.global_status

WHERE variable_name ='innodb_os_log_written'

UNION ALL

SELECT sleep(60)

UNION ALL

SELECT @a2 := variable_value AS a2

FROM information_schema.global_status

WHERE variable_name = 'innodb_os_log_written';

select ROUND((@a2-@a1)/1024/1024/@@innodb_log_files_in_group) as MB;


——————————

得到的MB * 30即可

innodb_log_buffer_size

决定InnoDB重做日志缓存池的大小 默认8M 对于产生大量更新记录的大事务 增加大小 可以避免InnoDB事务提交前就执行不必要的日志写入磁盘 so 在一个事务中更新 插入 删除大量记录的应用 可以通过增大log_buffer_size来减少写操作 提高性能

调整并发相关参数

1.调整max_connections 提高并发连接:

默认151 若状态变量connction_errors_max_conncetions不为0 一直增长 说明不断有连接请求因数据库连接数已达到最大允许值而失败 增大max_connections

linux平台下 支持500~1000不是难事 内存足够 不考虑响应时间 甚至达到上万 

windows下 受到线程库影响 最大连接数有以下限制:

(open tables*2+open connections)<2048

每个session 操作mysql数据库表都需要占用文件描述符 数据库连接本身也要占用文件描述符 因此,增大max_connections时 要注意评估open-files-limit的设置是否够用

2.调整back_log

控制Mysql监听TCP端口设置的积压请求栈大小 默认值50+(max_connections/5) 最大不超过900

如果需要数据库在较短时间处理大量连接请求 可以考虑适当增加back_log值

3.调整table_open_cache

每一个SQL执行线程只是打开1个表缓存 参数table_open_cache控制所有SQL执行线程可打开表缓存的数量

这个参数的值根据最大连接数 max_connections以及每个连接执行关联查询中 涉及表的最大个数来设定

max_connections *N

未执行flush tables时候 若MySQL状态变量opened_tables值比较大 说明table_open_cache设置小了

4.thread_cache_size设置

为加快连接数据库速度 mysql缓存一定数量的客户服务线程备用 通过参数thread_cache_size可控制mysql缓存客户服务线程的数量。

可通过计算线程cache的失效率thread_created/connections来衡量thread_cache_size的设置是否合适 越接近1 说明命中率低 可增大该参数

5.innodb_lock_wait_timeout设置

控制事务等待行锁时间 默认50ms 可以根据需要动态设置 对于需要快速反馈的交互式OLTP应用 可以将行锁等待超时时间调小 避免事务长时间挂起;对于后台批处理 可以将行锁等待时间调大 避免发生大的回滚操作

猜你喜欢

转载自blog.csdn.net/ichglauben/article/details/81269126