可以看到,InnoDB 存储引擎主要分三大部分:
- 内存结构
- 后台线程
- InnoDB 存储引擎文件
一、 内存结构
内存结构分为四部分:
- Buffer Pool
- Change Buffer(Insert buffer part of buffer pool)
- Adaptive Hash Index
- Log Buffer (Redo log buffer)
1. Buffer Pool
在启动时分配的内存区域,其大小由innodb_buffer_pool_size决定,缓存表与索引数据。对于专用的DB服务器,建议将60%~80%的物理内存分给此部分。
2. Change Buffer
缓存受DML操作影响的非唯一辅助索引(旧版本只能缓存insert,因此这部分之前也叫insert buffer)。如果其他读操作从磁盘中加载数据页到buffer pool时,这些页中正好也包含了change buffer中缓存的更改操作页,io_ibuf_thread线程会将change buffer中的记录真正合并到辅助索引中。
3. Adaptive Hash Index
用于管理缓冲池中的内部数据结构,并对缓冲池中的相关工作负载和内存操作组合进行自我调节。
4. Log Buffer
保存将要写入redo log的数据的内存区域,其大小由innodb_log_buffer_size决定,建议4~16M。innodb_flush_log_at_trx_commit参数决定何时将log buffer中内容写入日志,innodb_flush_log_at_timeout控制redolog刷新频率。
二、 后台线程
通过performance_schema.threads可以查询所有线程信息
select name,type,thread_id,processlist_id from performance_schema.threads where type='BACKGROUND';
可以看到MySQL后台线程一共有16种,其中最重要的有四大类——Master Thread、IO Thread、Purge Thread、Page Cleaner Thread。下面分别介绍名称及主要用途
类别 | 名称 | 用途 |
---|---|---|
Master Thread | srv_master_thread | InnoDB 存储引擎主线程。 5.6前:将buffer pool数据写入数据文件、执行检查点、undo页清理操作 5.6开始:工作大大减轻,主要通过4个循环来调用其他线程完成各项操作 |
IO Thread | io_ibuf_thread | 插入缓冲线程(ibuf指insert buffer)。负责change buffer的合并操作 |
io_read_thread | 读IO操作线程,负责数据库的异步读取操作。数量由innodb_read_io_threads设置,默认为4 | |
io_write_thread | 写IO操作线程,负责数据库的异步写操作。数量由innodb_write_io_threads设置,默认为4 | |
io_log_thread | 日志线程,用于将redo log刷新到磁盘(lgwr) | |
Purge Thread | srv_purge_thread | undo清理线程,负责undo页清理操作。5.6之后新增,以减轻srv_master_thread负载 |
Page Cleaner Thread | page_cleaner_thread | 脏页清理线程,负责刷新脏页。5.6之后新增,以减轻srv_master_thread负载 |
Other | srv_error_monitor_thread | 错误监控线程,负责错误控制及处理 |
srv_lock_timeout_thread | 锁线程,负责锁控制和死锁检测等 | |
thread_timer_notifier | 计时器过期通知线程,一般在超过max_execution_time后自动终止sql执行 | |
main | MySQL服务主线程(注意区别于InnoDB 存储引擎主线程),负责初始化、读取配置文件等功能 | |
srv_monitor_thread | InnoDB 监控打印进程,如果开启了InnoDB 监控器,会每隔5秒打印一次其采集的信息 | |
srv_worker_thread | InnoDB 工作线程。负责实际工作的线程,轮询从队列中取出任务(select,insert)等并执行 | |
buf_dump_thread | 缓冲池导入/出线程,负责缓冲池热点数据页导入/出 | |
dict_stats_thread | InnoDB 后台统计线程,负责InnoDB 表统计信息更新 | |
signal_handler | 信号处理线程,负责SIGTERM,SIGQUIT,SIGHUP信号处理的线程 |
三、 InnoDB 存储引擎文件
实际上只包含两大类数据 —— 表空间文件 和 redo log。
表空间可再细分为:
- 系统表空间
- 独立表空间
- 常规表空间(5.7新增)
- undo表空间
- temp表空间(5.7新增)
1. 系统表空间
文件名为 .ibdata*,可被多个用户表共享。默认只会创建一个名为ibdata1的系统表空间文件,可以使用innodb_data_file_path参数指定系统表空间数量和大小。
系统表空间包含以下内容:
- InnoDB数据字典(InnoDB相关对象元数据)
- doublewrite buffer,防止“部分写”问题(pg则采用全页写机制),名字叫buffer但实际在磁盘上
- undo segment(至少一个),用途同Oracle
2. 独立表空间(File-Per-Table Tablespaces)
设置innodb_file_per_table=1启用。若启用,则每个表都会生成一个.ibd文件用于存放自己的索引和数据;否则会统一存放在ibdata1系统表空间文件中。
3. 常规表空间(General Tablespaces)
5.7新功能,用户使用create tablespace语法创建的InnoDB共享表空间,文件名为tbs_name.ibd。
可使用 create table tb1 ... tablespace=xxx 或 alter table tb1 ... tablespace=xxx 将表添加到常规表空间中。
4. undo表空间
文件名为 undo* ,用途与Oracle相同,文件个数由innodb_undo_tablespaces控制。
InnoDB共支持128个undo段,其中32个undo段位于temp表空间,至少1个位于系统表空间,其余的位于undo表空间(最多95个)。
每个undo段有1023个事务槽位,并行事务场景中每个槽位对应一个事务。也就是说,对临时表操作的最大并行事务数为32*1023;对非临时表操作的最大并行事务数为96*1023。
5. temp表空间
存放非压缩的InnoDB临时表和相关对象,可通过innodb_temp_data_file_path参数定义文件名及初始大小,若未设置则默认创建一个初始12MB的名为ibtmp1的可自动扩展文件。
temp表空间在每次MySQL重启时会重新创建,如果无法创建,MySQL会启动失败。
temp表空间相关元数据存放在 information_schema.innodb_temp_table_info 表中。
6. redo log
用途同Oracle,默认会在磁盘中创建一组名为ib_logfile0和ib_logfile1的文件。
四、 update语句执行大致流程
假设正在执行以下语句
update test set idx=2 where id=10;
- 在Server层进行权限验证、解析、语法验证、生成并选出较优的执行计划。
- 到InnoDB引擎层,判断id=10的数据是否在buffer pool中。如果不在,需要从datafile中读入buffer pool,并对相关记录加独占锁;如果在,则略过读入的步骤。
- 将idx修改前的值和对应主键、事务id信息写入undo表空间的回滚段中。
- 更改缓冲池中对应页的数据,并将更新记录和新生成的LSN值写入log buffer中,更新后缓冲池中的该页就变为了脏页。
- 提交事务时,先将log buffer中的更新数据刷新的redo log中,然后写binlog,对binlog进行提交(同步到磁盘),binlog同步后就将binlog文件名和位置也写入redo log。然后在redo log中写入一个commit标记,此时才真正完成了事务的提交。提交完后释放独占锁。
- 如果开启了doublewrite buffer功能,刷新脏页时会先写一份到doublewrite buffer中,当doublewrite buffer中的数据落盘后,再从缓冲池把脏页刷新到数据文件。如果未开启,则直接由后台线程择机从缓冲池把脏页刷新到数据文件。
五、 MySQL 前台线程
前台线程也可通过performance_schema.threads可以查询
select name,type,thread_id,processlist_id from performance_schema.threads where type='FOREGROUND';
前台线程主要有5种,主要功能如下:
名称 | 用途 |
---|---|
compress_gtid_table | GTID压缩线程。用于压缩5.7新增的mysql.gtid_executed表中的GTID记录数 |
one_connection | 用户连接线程,用于处理用户请求 |
slave_io | IO线程,负责拉取主库binlog |
slave_sql | SQL线程,单线程复制中负责apply主库binlog。 注意在多线程复制中,slave_sql作为协调器线程将binlog分发给slave_worker线程实际进行apply,而它负责对多个slave_worker进行协调 |
slave_worker | 工作线程。多线程复制中,复制实际apply slave_sql线程分发的binlog |
思维导图概要如下:
参考
《MySQL 性能优化金字塔法则》
《MySQL 技术内幕 InnoDB 存储引擎》