1. MySQL 分层模型
2. Server层线程
2.1 用户线程
客户端连接 MySQL 后,MySQL 服务端跟客户端交互的线程,客户端跟服务端线程 1:1
2.1.1 thread_cache_size
- 参数:thread_cache_size
- 默认值:min(8+max_connections/100, 100)
- 作用:将用户线程缓存
2.1.2 用户线程三种状态
- 等连接:blocked_util_new_connection(connecting)
- 长链接空闲:protocol_classic::read_packet(sleep)
- 执行状态:mysql_execute_command
2.2 监听线程
负责监听新连接请求,从 thread_cache 或者新建线程接管,Mysqld_socket_listener::listen_for_connection_event
密码验证逻辑是在监听线程做的还是用户线程做的?答案是用户线程
2.3 两个跟 GTID 相关的线程
2.3.1 compress_gtid_table
只在开启 gtid,但是不开 binlog 时才工作,可以不用管
2.3.2 gtid_executed_compression_period
gtid 压缩的线程
Clone_persist_gtid::periodic_write:将 gtid 持久化,有两个参数:
- s_time_threshold=100(100ms 持久化一次)
- s_compression_threshold=50(每 50 个 gtid 做一次压缩)
2.4 事件调度器线程(Event_scheduler)
MySQL 会将待执行的 event 保存在队列中,时间调度线程会轮询队列,拿出 event 给创建线程来做
2.5 信号处理线程
2.6 用户线程占用内存大户
2.6.1 binlog_cache_size(row)、binlog_stmt_cache_size(statement)
这两个配置是线程 binlog 缓存大小,分别表示 row 和 statement 格式缓存大小,线程独占
- 申请时机:会话中第一次要写 binlog 时,两个一起申请,申请定义值
- 回收时机:回收连接到 thread_cache 或销毁时,一起回收
一般建议设置为 1M、2M、10M 等不要太小的值,下面例子说明 bing_log_cache 的分配策略
binlog_cache_size 和 binlog_stmt_cache_size 默认值 32k,库里有个空表t
connect
T0: 0K
truncate table t;
T1: 64K
T2: 64k
truncate table t;
T3: disconnect 64k
set global binlog_cache_size=3G
set global binlog_stmt_cache_size=3G
T4: 0K
connect0K
T5: 6G
truncate table t; T6:
2.6.2 sort buffer(join buffer、tmp_tale)
- 申请时机:语句执行过程中需要相应操作时申请
- 回收时机:语句操作完成后回收
虽然用完就释放,但是要注意在并发高的情况下,可能出现内存占用过大的情形
下面例子说明 sort buffer 分配策略
connect
set sort_buffer_size=32k;
T0: 0K
select ... order //一个能够吃满sort_buffer 的SQL 32K
T1: 0K
set sort_buffer_size=3G
select ... order //一个能够吃满sort_buffer 的SQL 3G
T2: 0K
disconnect
2.7 如何尽量减少长连接数量
我们常用的客户端连接池其实是没法减少 MySQL 服务端连接数的,只能加个中间层代理,所有请求走代理,然后代理创建一个连接池才能真正减少连接数量
这种情形下,连接是复用的,需要代理保存每个请求连接相关的参数,然后在执行时先保存现场,然后加载对应请求的现场,这样才能保证请求顺利执行
2.8 MySQL 线程池(企业版才有,付费内容,mariadb 免费提供)
中间层连接池能减少 MySQL 的连接数,在 MySQL 5.6 版本之前,没有引入线程池机制,默认情况下每个客户端连接会对应一个独立的线程来处理。
从 MySQL 5.6 版本开始引入了线程池机制。在使用线程池的情况下,连接与线程不再是一一对应的关系。线程池预先创建了一定数量的线程并将它们组织成多个线程组(worker 线程组)。客户端连接的请求会被放入任务队列中,线程池中的线程会从任务队列中取出任务进行处理。多个连接的请求可以被不同的线程依次处理,一个线程也可以在不同时刻处理不同连接的请求,实现了线程的复用。
如上所示,client 有很多,连接也有很多,但是 MySQL 干活的线程是一定的,这跟 epoll 很像,一个线程用于处理连接请求,其余数据收发交给线程池来做,这样能实现少量线程服务多数连接的目的,同样的,worker 线程也需要保存请求执行的上下文,也就是图中会话相关的对象
始终要有一个概念,连接≠线程
3. InnoDB 线程
MySQL 中跟 innodb 相关的线程可以通过select thread_id,name from performance_schema.threads where name like '%innodb%' order by name;来查询
| thread_id | name |
|---|---|
| 39 | thread/innodb/buf_dump_thread |
| 29 | thread/innodb/buf_resize_thread |
| 40 | thread/innodb/clone_gtid_thread |
| 31 | thread/innodb/dict_stats_thread |
| 32 | thread/innodb/fts_optimize_thread |
| 4 | thread/innodb/io_ibuf_thread |
| 5 | thread/innodb/io_read_thread |
| 6 | thread/innodb/io_read_thread |
| 7 | thread/innodb/io_read_thread |
| 8 | thread/innodb/io_read_thread |
| 9 | thread/innodb/io_read_thread |
| 10 | thread/innodb/io_read_thread |
| 11 | thread/innodb/io_write_thread |
| 12 | thread/innodb/io_write_thread |
| 13 | thread/innodb/io_write_thread |
| 14 | thread/innodb/io_write_thread |
| 16 | thread/innodb/log_checkpointer_thread |
| 21 | thread/innodb/log_files_governor_thread |
| 17 | thread/innodb/log_flush_notifier_thread |
| 18 | thread/innodb/log_flusher_thread |
| 19 | thread/innodb/log_write_notifier_thread |
| 20 | thread/innodb/log_writer_thread |
| 15 | thread/innodb/page_flush_coordinator_thread |
| 27 | thread/innodb/srv_error_monitor_thread |
| 26 | thread/innodb/srv_lock_timeout_thread |
| 30 | thread/innodb/srv_master_thread |
| 28 | thread/innodb/srv_monitor_thread |
| 41 | thread/innodb/srv_purge_thread |
3.1 buf_dump_thread
参数 innodb_buffer_pool_dump_now 的值永远是 off,执行语句 set global innodb_buffer_pool_dump_now = on,作用是执行一次 buf_dump,然后该值仍旧为 off,执行 buf_dump 就是 buf_dump_thread 来做的
文件内容如下:
参数 innodb_buffer_pool_dump_pct=x 将 buffer pool instance 中的 LRU 链表前 x% 存入 dump 文件,作用是当 MySQL 重启时,能读该文件来恢复一部分 LRU,相当于预热,避免重启后 buffer_pool 命中率下降很多
建议修改为 62.5,即 5/8,原因是 buffer_pool 中分为新老,其中新占 5/8,也就是非常活跃的数据
这个特性特别适合存储计算分离的场景,以亚马逊欧若拉为例,新增读实例时,直接将该 dump 文件传输给新增的实例恢复 buffer_pool,这样能保证新库速度很快,但是对于一般的主从结构,是不能将主库的 dump 文件给从库用的,因为磁盘上数据位置不同
3.2 ib_io_read
这不是一个线程,这是一个 InnoDB 存储引擎执行的磁盘读取操作,有多种线程都能执行该操作,该操作的触发有以下几种:
- 线性预读 innodb_read_ahead_threshold,默认值为 54,表示如果顺序读了 54 个页,那就将整个段后面的 10 页一起预读
- 随机预读 innodb_random_read_ahead,默认值为 off,表示一个段如果有 13 个页被读了,就将整个段剩余的页都预读, 建议关闭
- buf_read_ibuf_merge_pages —— io_ibuf_thread,作用是将 change buffer 的内容应用到页,比如从磁盘读取页,然后应用 change buffer,页变为脏页等待刷盘
3.3 page_flush_coordinator_thread
innodb_page_cleaner 默认值为 4,即有四个线程在做 flush 刷脏的动作,默认要求是不超过 innodb_buffer_pool_instances
3.4 purge thread
purge 是用来清理 undo 的
问答
-
为什么 OLTP 型基本要求 MySQL buffer pool 命中率要达到 99%,怎么能达到?
buffer pool 的大小肯定是小于磁盘空间的,比如磁盘有 1T,buffer pool 只有 128GB,大概是 1/10,那么怎么用 1/10 的空间实现 99% 左右的命中率呢?这是由于二八原则的原因,不是所有数据都是频繁访问的数据,如果是正常业务,一般只会访问其中 20% 左右的数据,因此能用 1/10 左右的内存实现 99% 左右的命中率
-
一个正常运行的实例,rm 掉 ibdata,会怎么样?
服务继续正常使用,但是下次重启时,会报错