为什么优化?
从业务需求出发,事实证实优化的结果往往会与期待值相反。优化有风险,涉足需谨慎!
优化的风险?
谁参与优化?
优化的方向?
优化的范围及思路?
解耦 & 切片 & 自愈
top - 13:37:57 up 3 min, 1 user, load average: 0.52, 0.74, 0.33Tasks: 160 total, 2 running, 158 sleeping, 0 stopped, 0 zombie %Cpu(s): 0.0 us, 0.7 sy, 0.0 ni, 99.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 stKiB Mem : 2895196 total, 1848656 free, 615172 used, 431368 buff/cache KiB Swap: 2097148 total, 2097148 free, 0 used. 2115012 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 710 root 20 0 295376 5200 3960 S 0.3 0.2 0:00.32 vmtoolsd 8679 mysql 20 0 1123976 182680 11168 S 0.3 6.3 0:01.75 mysqld 8725 root 20 0 162100 2268 1580 R 0.3 0.1 0:00.12 top 1 root 20 0 125772 4196 2604 S 0.0 0.1 0:01.68 systemd 2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd 3 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kworker/0:0 ...
CPU
多CPU监控:主要判断CPU多核心有没有被充分利用。例如单颗CPU很忙,其他CPU很闲,对于MySQL来讲,有可能是并发参数设定不合理导致的。
MEM
SWAP
在swappiness表示内存剩余量在30%时使用swap,即内存使用70%时使用swap。
[root@db01 ~]# cat /proc/sys/vm/swappiness 30
对于MySQL来说,建议将swappiness设置为0(即内存使用完才会使用swap),因为使用swap会影响IO性能。
# 临时修改 [root@db01 ~]# echo 0 >/proc/sys/vm/swappiness # 永久修改 [root@db01 ~]# vim /etc/sysctl.conf vm.swappiness=0 [root@db01 ~]# sysctl -p
IO
扩展类深度优化
主机
CPU
内存:建议2-3倍CPU核心数量 (ECC)
磁盘:SATA-III 、SAS 、 Fc 、SSD(sata)、pci-e 、 SSD 、 Flash
注意:主机 RAID卡的BBU(Battery Backup Unit)关闭
存储:根据存储数据种类的不同,选择不同的存储设备,配置合理的RAID级别(RAID5、RAID10、热备盘)
网络
SWAP调整
# 临时修改 [root@db01 ~]# echo 0 >/proc/sys/vm/swappiness # 永久修改 [root@db01 ~]# vim /etc/sysctl.conf vm.swappiness=0 [root@db01 ~]# sysctl -p
swappiness参数决定了Linux是倾向于使用swap,还是倾向于释放文件系统cache。在内存紧张的情况下,swappiness数值越低越倾向于释放文件系统cache。当然,这个参数只能减少使用swap的概率,并不能避免Linux使用swap。
修改MySQL的配置参数innodb_flush_method,开启O_DIRECT模式。这种情况下,InnoDB的buffer pool会直接绕过文件系统cache来访问磁盘,但是redo log依旧会使用文件系统cache。值得注意的是,Redo log是覆写模式的,即使使用了文件系统的cache,也不会占用太多。
IO调度策略
# centos 7 默认是deadline [root@db01 ~]# cat /sys/block/sda/queue/scheduler noop [deadline] cfq
CentOS 6修改为deadline
# 临时修改为deadline(centos6) [root@db01 ~]# echo deadline >/sys/block/sda/queue/scheduler # 永久修改为deadline(centos6) [root@db01 ~]# vi /boot/grub/grub.conf kernel /boot/vmlinuz-2.6.18-8.el5 ro root=LABEL=/ elevator=deadline rhgb quiet
IO :提前规划好以下所有问题,减轻MySQL优化的难度
简介
Mysql的最大连接数,如果服务器的并发请求量比较大,可以调高这个值,当然这是要建立在机器能够支撑的情况下,因为如果连接数越来越多,mysql会为每个连接提供缓冲区,就会开销的越多的内存,所以需要适当的调整该值,不能随便去提高设值。
查看
# 查看最大连接数 mysql> show variables like 'max_connections'; +-----------------+-------+ | Variable_name | Value | +-----------------+-------+ | max_connections | 151 | +-----------------+-------+ 1 row in set (0.00 sec) # 启动以来最大连接数量 mysql> show status like 'Max_used_connections'; +----------------------+-------+ | Variable_name | Value | +----------------------+-------+ | Max_used_connections | 3 | +----------------------+-------+ 1 row in set (0.04 sec)
判断
配置
# 临时修改 mysql> set GLOBAL max_connections=1024; # 永久修改 [root@db01 ~]# vim /etc/my.cnf Max_connections=1024
mysql> show full processlist;当发现大量的待连接进程时,就需要加大back_log或者加大max_connections的值。
[root@db01 ~]# vim /etc/my.cnf back_log=1024
简介
mysql> show variables like 'wait_timeout'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | wait_timeout | 28800 | +---------------+-------+ 1 row in set (0.26 sec)
mysql> show variables like 'interactive_timeout'; +---------------------+-------+ | Variable_name | Value | +---------------------+-------+ | interactive_timeout | 28800 | +---------------------+-------+ 1 row in set (0.02 sec)
例如:在终端上进行mysql管理,使用的即时交互的连接,这时候,如果没有操作的时间超过了interactive_time设置的时间就会自动的断开(默认的是28800,可调优为7200)。
建议
wait_timeout:如果设置太小,那么连接关闭的就很快,从而使一些持久的连接不起作用。如果设置太大,容易造成连接打开时间过长,在show processlist时候,能看到很多的连接 ,一般希望wait_timeout尽可能低。
一般将wait_timeout设定比较小,interactive_timeout要和应用开发人员沟通长连接应用(长连接应用:为了不去反复的回收和分配资源,降低额外的开销)是否很多。如果需要,那么这个值可以不调整。另外还可以使用类外的参数弥补。
配置
wait_timeout=60 interactive_timeout=1200
简介
key_buffer_size指定索引缓冲区的大小,它决定索引处理的速度,尤其是索引读的速度。
mysql> show variables like "key_buffer_size%"; +-----------------+---------+ | Variable_name | Value | +-----------------+---------+ | key_buffer_size | 8388608 | +-----------------+---------+ 1 row in set (0.58 sec)
判断
通过key_read_requests和key_reads可以判断key_baffer_size设置是否合理。
# 一共10个索引读取请求,有5个请求在内存中没有找到就直接从硬盘中读取索引。 mysql> show status like "key_read%"; +-------------------+-------+ | Variable_name | Value | +-------------------+-------+ | Key_read_requests | 10 | | Key_reads | 5 | +-------------------+-------+ 2 rows in set (1.62 sec)
注:key_buffer_size只对myisam表起作用,即使不使用myisam表,但是内部的临时磁盘表是myisam表,也要使用该值。
可以使用检查状态值created_tmp_disk_tables得知:
mysql> show status like "created_tmp%"; +-------------------------+-------+ | Variable_name | Value | +-------------------------+-------+ | Created_tmp_disk_tables | 0 | # 磁盘创建临时表的个数 | Created_tmp_files | 6 | # 临时文件(暂时忽略) | Created_tmp_tables | 1 | # 内存创建临时表的个数 +-------------------------+-------+ 3 rows in set (0.10 sec)
通常习惯以
Created_tmp_tables/(Created_tmp_disk_tables + Created_tmp_tables) 内存创建比例
Created_tmp_disk_tables/(Created_tmp_disk_tables + Created_tmp_tables) 磁盘创建比例(控制在5%-10%以内)
或者各自的一个时段内的差额计算,来判断基于内存的临时表利用率。所以,我们会比较关注 Created_tmp_disk_tables 是否过多,从而认定当前服务器运行状况的优劣。
注意:mysqldump时会用到临时表。
key_buffer_size=64M
简介
查询缓存简称QC,使用查询缓冲,mysql将查询结果存放在缓冲区中,今后对于同样的select语句(区分大小写),将直接从缓冲区中读取结果。
MySQL 5.7 默认关闭该参数;
MySQL 8.0摒弃该参数。
存储方式:
select * from t1 where id=10;
使用方式:
一个sql查询如果以select开头,那么mysql服务器将尝试对其使用查询缓存。
注意:两个sql语句,只要相差哪怕是一个字符(列如大小写不一样;多一个空格等),那么这两个sql将使用不同的一个cache。
判断
mysql> show status like "%Qcache%"; +-------------------------+---------+ | Variable_name | Value | +-------------------------+---------+ | Qcache_free_blocks | 1 | | Qcache_free_memory | 1031832 | | Qcache_hits | 0 | | Qcache_inserts | 0 | | Qcache_lowmem_prunes | 0 | | Qcache_not_cached | 9 | | Qcache_queries_in_cache | 0 | | Qcache_total_blocks | 1 | +-------------------------+---------+ 8 rows in set (0.02 sec)
Qcache_free_blocks:缓存中相邻内存块的个数。如果该值显示较大,则说明Query Cache 中的内存碎片较多了,FLUSH QUERY CACHE会对缓存中的碎片进行整理,从而得到一个空闲块。
注意:当一个表被更新之后,和它相关的cache blocks将被free。但是这个block依然可能存在队列中,除非是在队列的尾部。可以用FLUSH QUERY CACHE语句来清空free blocks
Qcache_free_memory:Query Cache 中目前剩余的内存大小。通过这个参数可以较为准确的观察出当前系统中的Query Cache 内存大小是否足够,是需要增加还是减少。
Qcache_hits:表示有多少次命中缓存。我们主要可以通过该值来验证我们的查询缓存的效果。数字越大,缓存效果越理想。
Qcache_inserts:表示多少次未命中然后插入,意思是新来的SQL请求在缓存中未找到,不得不执行查询处理,执行查询处理后把结果insert到查询缓存中。这样的情况的次数越多,表示查询缓存应用到的比较少,效果也就不理想。当然系统刚启动后,查询缓存是空的,这很正常。
Qcache_lowmem_prunes:多少条Query因为内存不足而被清除出QueryCache。通过“Qcache_lowmem_prunes”和“Qcache_free_memory”相互结合,能够更清楚的了解到我们系统中Query Cache 的内存大小是否真的足够,是否非常频繁的出现因为内存不足而有Query 被换出。这个数字最好长时间来看;如果这个数字在不断增长,就表示可能碎片非常严重,或者内存很少。(上面的free_blocks和free_memory可以告知属于哪种情况)
Qcache_not_cached:不适合进行缓存的查询的数量,通常是由于这些查询不是 SELECT 语句或者用了now()之类的函数。
Qcache_queries_in_cache:当前Query Cache 中cache 的Query 数量;
Qcache_total_blocks:当前Query Cache 中的block 数量;。
Qcache_hits / (Qcache_inserts+Qcache_not_cached+Qcache_hits)
90/ 10000 0 90
如果出现hits比例过低,可以关闭查询缓存,使用redis专门缓存数据库。
配置
mysql> show variables like '%query_cache%' ; +------------------------------+---------+ | Variable_name | Value | +------------------------------+---------+ | have_query_cache | YES | | query_cache_limit | 1048576 | | query_cache_min_res_unit | 4096 | | query_cache_size | 1048576 | | query_cache_type | OFF | | query_cache_wlock_invalidate | OFF | +------------------------------+---------+ 6 rows in set (0.00 sec)
配置说明:query_cache_type为off表示不缓存任何查询
字段解释:
修改/etc/my.cnf
query_cache_size=128M query_cache_type=1
max_connect_errors=2000
简介
每个需要进行排序的线程都分配sort_buffer_size设置大小的一个缓冲区(消耗内存)。
判断
Sort_Buffer_Size并不是越大越好,由于是connection级的参数,过大的设置+高并发可能会耗尽系统内存资源。
配置
sort_buffer_size=1M
max_allowed_packet=32M
简介
服务器线程缓存个数,这个值表示可以重新利用保存在缓存中线程的数量。当断开连接时,那么客户端的线程将被放到缓存中以响应下一个客户而不是销毁(前提是缓存数未达上限)。如果线程重新被请求,那么请求将从缓存中读取,如果缓存中是空的或者是新的请求,那么这个线程将被重新创建。如果有很多新的线程,增加这个值可以改善系统性能。
判断
通过比较 Connections 和 Threads_created 状态的变量,可以看到这个变量的作用。
设置规则:1GB 内存配置为8,2GB内存配置为16,3GB内存配置为32,4GB或更高内存,可配置更大线程数。
mysql> show status like 'threads_%'; +-------------------+-------+ | Variable_name | Value | +-------------------+-------+ | Threads_cached | 1 | | Threads_connected | 2 | | Threads_created | 3 | | Threads_running | 2 | +-------------------+-------+ 4 rows in set (0.27 sec)
配置
thread_cache_size=32
总结
简介
对于InnoDB表来说,innodb_buffer_pool_size的作用就相当于key_buffer_size对于MyISAM表的作用一样。
判断
InnoDB使用该参数指定大小的内存来缓冲数据和索引。对于单独的MySQL数据库服务器,最大可以把该值设置成物理内存的80%(一般建议不要超过物理内存的70%)。
配置
innodb_buffer_pool_size=2048M
innodb_flush_log_at_trx_commit=1
简介
此参数用来设置innodb线程的并发数量,默认值为0表示不限制。
判断
官方建议:
设置标准:
配置
innodb_thread_concurrency=8
方法
innodb_log_buffer_size=128M
innodb_log_file_size=100M
innodb_log_files_in_group=3
read_buffer_size=1M
read_rnd_buffer_size=1M
bulk_insert_buffer_size=8M
log-bin=/data/mysql-bin binlog_format=row binlog_cache_size=2M # 为每个session 分配的内存,在事务过程中用来存储二进制日志的缓存, 提高记录bin-log的效率。没有什么大事务,dml也不是很频繁的情况下可以设置小一点,如果事务大而且多,dml操作也频繁,则可以适当的调大一点。前者建议是0-1M,后者建议是 2-4M max_binlog_cache_size=8M # binlog 能够使用的最大cache 内存大小 max_binlog_size=512M # 指定binlog日志文件的大小,默认值是1GB。如果当前的日志大小达到max_binlog_size,会自动创建新的二进制日志。在导入大容量的sql文件时,建议关闭sql_log_bin,否则硬盘扛不住,且建议定期做删除。 expire_logs_days=7 # 定义mysql清除过期日志的时间,默认值为0。 # 双1标准(基于安全的控制) sync_binlog=1 innodb_flush_log_at_trx_commit=1
set sql_log_bin=0; show status like 'com_%';
Innodb_flush_method=(O_DIRECT, fsync)
最安全模式
innodb_flush_log_at_trx_commit=1 innodb_flush_method=O_DIRECT
最高性能模式
innodb_flush_log_at_trx_commit=0 innodb_flush_method=fsync
一般情况下,更偏向于安全模式 “双一标准”
innodb_flush_log_at_trx_commit=1 sync_binlog=1 innodb_flush_method=O_DIRECT
[mysqld] basedir=/data/mysql datadir=/data/mysql/data socket=/tmp/mysql.sock log-error=/var/log/mysql.log log_bin=/data/binlog/mysql-bin binlog_format=row skip-name-resolve server-id=52 gtid-mode=on enforce-gtid-consistency=true log-slave-updates=1 relay_log_purge=0 max_connections=1024 back_log=128 wait_timeout=60 interactive_timeout=7200 key_buffer_size=16M query_cache_size=64M query_cache_type=1 query_cache_limit=50M max_connect_errors=20 sort_buffer_size=2M max_allowed_packet=32M join_buffer_size=2M thread_cache_size=200 innodb_buffer_pool_size=1024M innodb_flush_log_at_trx_commit=1 innodb_log_buffer_size=32M innodb_log_file_size=128M innodb_log_files_in_group=3 binlog_cache_size=2M max_binlog_cache_size=8M max_binlog_size=512M expire_logs_days=7 read_buffer_size=2M read_rnd_buffer_size=2M bulk_insert_buffer_size=8M [client] socket=/tmp/mysql.sock
session1
mysql> use pressure; Database changed mysql> UPDATE t_100w SET k1='av' WHERE id=10; Query OK, 1 row affected (8.93 sec) Rows matched: 1 Changed: 1 Warnings: 0
session2
mysql> use pressure; Database changed mysql> UPDATE t_100w SET k1='az' WHERE id=10; Query OK, 1 row affected (6.79 sec) Rows matched: 1 Changed: 1 Warnings: 0
查看有没有锁等待:SHOW STATUS LIKE ‘innodb_row_lock%’;
mysql> SHOW STATUS LIKE 'innodb_row_lock%'; +-------------------------------+-------+ | Variable_name | Value | +-------------------------------+-------+ | Innodb_row_lock_current_waits | 1 | | Innodb_row_lock_time | 15729 | | Innodb_row_lock_time_avg | 3145 | | Innodb_row_lock_time_max | 5221 | | Innodb_row_lock_waits | 5 | +-------------------------------+-------+ 5 rows in set (0.37 sec)
查看哪个事务在等待(阻塞):SELECT * FROM information_schema.INNODB_TRX WHERE trx_state=‘LOCK WAIT’\G
mysql> SELECT * FROM information_schema.INNODB_TRX WHERE trx_state='LOCK WAIT'\G *************************** 1. row *************************** trx_id: 10003066 # 事务ID trx_state: LOCK WAIT # 当前事务状态 ... trx_mysql_thread_id: 1114 # 连接线程ID(show processllist) trx_query: UPDATE t_100w SET k1='az' WHERE id=10 # 当前被阻塞的操作 ...
查看锁源 :SELECT * FROM sys.innodb_lock_waits\G
mysql> SELECT * FROM sys.innodb_lock_waits\G locked_table: `pressure`.`t_100w` # 出现锁的表 waiting_trx_id: 10003066 # 等待的事务ID waiting_pid: 1114 # 等待的线程ID blocking_trx_id: 10003069 # 锁源的事务ID blocking_pid: 5 # 锁源的线程号
查看锁源thread_id (通过连接线程ID获得SQL线程ID) :SELECT * FROM performance_schema.threads WHERE processlist_id=blocking_pid;
mysql> SELECT * FROM performance_schema.threads WHERE processlist_id=5\G THREAD_ID: 30 ...
查看锁源SQL语句(通过SQL线程ID获得SQL语句)
当前在执行SQL语句:SELECT * FROM performance_schema.`events_statements_current` WHERE thread_id=30; 历史执行的SQL语句:SELECT * FROM performance_schema.`events_statements_history` WHERE thread_id=30;
mysql> SELECT * FROM performance_schema.`events_statements_current` WHERE thread_id=30\G *************************** 1. row *************************** THREAD_ID: 30 EVENT_ID: 60000102 END_EVENT_ID: 60000102 EVENT_NAME: statement/sql/update SOURCE: socket_connection.cc:101 TIMER_START: 16584453793783000 TIMER_END: 16596271775695000 TIMER_WAIT: 11817981912000 LOCK_TIME: 165000000 SQL_TEXT: UPDATE t_100w SET k1='av' WHERE id=10 DIGEST: a53700ae3d083482445ba277e43f7eee DIGEST_TEXT: UPDATE `t_100w` SET `k1` = ? WHERE `id` = ? CURRENT_SCHEMA: pressure OBJECT_TYPE: NULL
# 死锁监控(只能查看最后一次死锁状态) mysql> show engine innodb status\G # 开启死锁日志 mysql> show variables like '%deadlock%'; [root@db01 ~]# vim /etc/my.cnf innodb_print_all_deadlocks = 1
背景:硬件环境 DELL R720,E系列16核,48G MEM SAS900G6,RAID10
例行巡检时,发现9-11点时间段的CPU压力非常高(80-90%)
项目职责
项目结果
经过排查处理,锁等待的个数减少80%,解决了CPU持续峰值的问题。
锁监控相关命令:
show status like 'innodb_rows_lock%' select * from information_schema.innodb_trx; select * from sys.innodb_lock_waits; select * from performance_schema.threads; select * from performance_schema.events_statements_current; select * from performance_schema.events_statements_history;
MySQL 5.7 从库多线程MTS
基本要求
gtid_mode=ON enforce_gtid_consistency=ON log_slave_updates=ON master_info_repository=TABLE relay_log_info_repository=TABLE relay_log_recovery=ON slave-parallel-type=LOGICAL_CLOCK slave-parallel-workers=16 # cpu核心数作为标准