第一章:MySQL体系结构和存储引擎
MySQL为单进程多线程
读取配置文件顺序,以最后一个为准 /etc/my.cnf -> etc/mysql/my.cnf -> /usr/local/mysql/etc/my.cnf -> /.my.cnf
数据库:文件的集合
数据库实例:用户与操作系统之间的数据管理程序
MySQL组成:
- 连接池组件
- 管理服务和工具组件
- SQL接口组件
- 查询分析器组件
- 优化器组件
- 缓冲组件
- 插件式存储引擎
- 物理文件
InnoDB:支持事务、非锁定读、外键;5.5.5开始的默认引擎。
MyISAM:不支持事务、表锁设计,支持全文索引,只缓存索引文件不缓存数据文件;5.5.8前的默认引擎。
MySQL的连接:本质上是进程通信,即连接进程和MySQL数据库实例进行通信。常见的通信方式有管道、命名管道、命名字、TCP/IP套接字、UNIX域套接字。
第二章:InnoDB存储引擎
OLTP(On-Line transaction processing):传统关系型数据库的主要应用,适合InnoDB。与之对应的是OLAP(On-Line Analytical Processing)。
后台线程
void master_thread(){
goto loop;
loop:
for( int i = 0 ; i < 10 ; i++ ){
thread_sleep(1) // sleep 1 秒
将日志缓冲刷新到磁盘
if( last_one_second_ios < 5 ){ // 最近1秒IO次数是否大于5
合并5个插入缓冲
}
if( buf_get_modified_ratio_pct > innodb_max_dirty_pages_pct ){
刷新100个脏页到磁盘
}
if( 没有用户活动即数据库空闲时 ){
goto backgroud loop
}
}
if( last_ten_second_ios < 200 ){ // 最近5秒IO次数是否大于200
刷新100个脏页到磁盘
}
至多合并5个插入缓冲
将日志缓冲刷新到磁盘
删除无用的Undo页
if( buf_get_modified_ratio_pct > 70% ){
刷新100个脏页到磁盘
}else{
刷新10个脏页到磁盘
}
goto loop
backgroud loop:
删除无用的Undo页
合并20个缓冲插入
if( 有用户活动 ){
goto loop:
}else{
goto flush loop
}
flush loop:
刷新100个脏页到磁盘
if(buf_get_modified_ratio_pct > innodb_max_dirty_pages_pct){
goto flush loop
}else{
goto suspend loop
}
suspend loop:
suspend_thread() // 挂起自身
等待事件
goto loop;
}
- Master Thread。拥有最高线程优先级别。即使某个事务还没提交,InnoDB依然每秒都将重做日志缓冲刷到重做日志文件,这就是为什么再大的事务提交时间都很短。
- IO Thread。负责异步IO的请求回调。
- Purge Thread。回收已经使用并分配的undo页。
- Page Cleaner Thread
硬编码,即通常所说的写死数据、魔法值,可通过枚举、常量、可用性配置进行优化。
内存:用来弥补磁盘速度与CPU速度的差距。内存/磁盘类似于Redis/MySQL。
包括缓冲池、重做日志缓冲、额外内存池。其中缓冲池包括数据页、索引页、插入缓冲、锁信息、自适应哈希索引、数据字典信息。数据页、索引页由LRU List、Free List、Flush List管理。
- 缓冲池。磁盘读取到的页会放在缓冲池中,下次读同样的页时,会判断缓冲池中是否有,没有才去读磁盘。
- LRU List(Last Recent Used 最近最少使用算法) / Free List / Flush List。
InnoDB并没有使用朴素的LRU算法,而是加入了midpoint,默认为距离尾部37%的位置,此点往前认为是热点数据/热端。其加入的原因是,某些SQL操作如索引、数据的扫描操作,可能会新增大量数据,使得大量数据被刷出。同时,InnoDB提供了一个时间参数,当数据加入达到这个时间时,即可从midpoint转移到热端。
LRU List用来管理已读取的页,当数据库刚启动时,LRU List为空,页存放在Free List中。被修改了的页为脏页,存放在LRU List和Flush List中。
- 重做日志缓冲
- 额外的内存池
Checkpoint技术
解决问题:
- 缩短数据库灾后重启时间
- 缓冲池不够用时,将脏页刷新到磁盘
- 重做日志不可用时,刷新脏页
如果LRU List溢出的页为脏页,那么需要强制执行Checkpoint进行刷盘。
Sharp Checkpoint。数据库关闭时将所有的脏页都刷盘,如果在数据库运行时使用,会大幅降低可用性。
Fuzzy Checkpoint
- Master Thread Checkpoint。异步,详见伪代码。
- FLUSH_LRU_LIST Checkpoint。为保证LRU list有可用页,如果没有,则会对LRU List尾端的页就行移除,其中有脏页即是Checkpoint。MySQL5.6之前,该操作发生在用户查询线程中,会阻塞用户查询,之后版本放在了Page Cleaner线程。
- Async/Sync Flush Checkpoint。重做日志文件不可用时,需将页刷到磁盘,保证重做日志的循环使用的可用性。MySQL5.6之前,该操作发生在用户查询线程中,会阻塞用户查询(Sync阻塞所有用户查询),之后版本放在了Page Cleaner线程。
- Dirty Page too much Checkpoint。脏页太多,强制Checkpoint。
聚集索引即主键(Primary Key),非聚集索引即非主键,辅助索引(Secondary Index)。非聚集索引叶子节点的插入不是顺序的,需要离散地访问非聚集索引页。
InnoDB关键特性
- 插入缓冲(Insert Buffer)。提升了性能。通常应用程序中行记录的插入顺序是按照主键递增的顺序进行的,大部分自增主键字段的插入操作是非常快的,而B+Tree的特性决定了非聚集索引插入的离散性。插入缓冲为:判断该非聚集索引页是否在缓冲池,如果在则插入到叶子节点,否则放在一个Insert Buffer对象中,定期进行合并。所需的条件为:索引是非唯一的辅助索引。
- 两次写(Double Write)。提高数据页的可靠性。当正在写入某个页到表中时,发生了宕机,造成部分写失效。由于这个页本身已经发生了损坏,无法通过重做日志恢复。再重做日志前还需要一个页的副本,先通过副本还原页,再进行重做,这就是两次写。
- 自适应哈希索引(Adaptive Hash Index)。InnoDB存储引擎会自动根据访问的频率和模式来为某些热点页建立哈希索引。当同样的查询条件执行大于100次,并且次数等于页中记录的十六分之一时会开启AHI。(联想:业务场景也可以用这个思路,对接口进行监控,而后将热点数据放在本地缓存/Redis中)
- 异步IO(Async IO)。对IO进行合并,并且是异步的。Windows和Linux系统提供内核级别的AIO支持,对于不支持的系统需要InnoDB自己代码实现。
- 刷新临近页(Flush Neighbor Page)。刷新脏页时,会将该页所在区的所有脏页一起刷新。易于实现AIO,更适用于传统机械硬盘,而对于有着超高IOPS性能的固态硬盘则建议关闭。
工作原理 | 读写速度 | 安全性 | 成本 | |
固态硬盘 | 半导体状态做记忆介质。 由主控缓存和闪存组成。 |
快 | 高 | 高 |
机械硬盘 | 磁做记忆介质的。 读取和写入时由磁头在转动的盘片上转动寻找文件所在扇区。 运行时马达高速运转,产生震动和可感噪音。 |
慢 | 低 | 低 |
第三章:文件
文件分类:参数文件、日志文件、socket文件、pid文件、MySQL表结构文件、存储引擎文件。
参数文件:以文本形式存储
日志文件
- 错误日志(error log)。用于排错。通过SHOW VARIABLES LIKE ’log_error‘查询。
- 二进制日志(binlog)。记录了所有对MySQL数据库的更改操作,即使操作没有引起变化也会写入。用于恢复、复制(主从同步)、审计(检查注入攻击)。默认不启动,启动性能下降1%。
max_binlog_size | 单个二进制日志文件最大值 |
binlog_cache_size | 所有未提交的二进制日志会记录到缓存中,当事务提交时直接将缓存的二进制文件写入二进制日志文件。参数表明缓冲的大小,默认32K。当大于这个大小时,会把日志写入临时文件。基于会话的,开启一个事务就会分配一个缓存空间。设置太小会造成大量临时文件,太大会浪费空间。 |
binlog_cache_user | 使用缓冲写二进制日志的次数 |
binlog_cache_disk_use | 临时文件写二进制日志的次数 |
sync_binlog | 每写多少次缓冲同步到磁盘。 0表示不做控制,交由操作系统来管理(联想:对比Redis和Kafka,Redis AOF默认是1秒同步一次数据,即便服务器宕机最多丢失1秒的数据;Kafka是立即同步,将数据持久化到日志中) 1表示采用同步写磁盘的方式来写二进制日志 |
innodb_support_xa | 1可以确保二进制日志和InnoDB存储引擎数据文件的同步 |
binlog_do_db / binlog_ignore_db | 写入或忽略写入哪些库的日志 |
binlog_format | 二进制日志的格式。 5.1前格式为基于SQL语句级别的,因此复制逻辑与Oracle的Standby相似。如果在主服务器上运行rand、uuid函数或触发器会引起主从数据不一致。 5.1后可设为STATEMENT、ROW、MIXED,分别为逻辑SQL语句、表的更改情况、混合使用。ROW对磁盘空间要求增加,复制时网络开销也因此增加。 |
- 慢查询日志(show query log)。用于调优。慢查询日志参数log_slow_queries默认不打开,需先开启。long_query_time默认为10s,即超过10s的SQL语句会被记录下来。log_queries_not_using_indexes表示是否记录没有使用索引的SQL语句,log_throttle_queries_not_using_indexes表示每分钟最多记录多少条没有使用索引的SQL语句,默认为0,即不限制。
物理读取指从磁盘进行IO读取,逻辑读取则包含磁盘和缓冲池。long_query_io表示逻辑IO次数超过这个数字的SQL会被记录到日志,show_query_type取值可见下表格。
0 | 不记录SQL到日志 |
1 | 根据运行时间记录 |
2 | 根据逻辑IO次数记录 |
3 | 根据运行时间和逻辑IO次数记录 |
- 查询日志(log)。记录了所有对MySQL数据库的请求信息,无论这些信息是否执行。
InnoDB存储引擎文件:每个表存储引擎还有自己独有的文件。
- 表空间文件。存储数据按表空间进行存放,默认为10MB的ibdata1文件。可指定多个文件为空间,以及文件的属性。
- 重做日志文件。记录InnoDB存储引擎的事务日志。默认数据目录下有ib_logfile()和ib_logfile1()的文件。每个InnoDB存储引擎至少有1个重做日志文件组,每个文件组下至少有2个重做日志文件。可通过设置多个不同磁盘上的镜像日志组,来提高可用性。日志组中每个文件大小一致,循环写入,一个写满了才写下一个。
innodb_log_file_size | 文件的大小。如果太大,恢复时需要很长时间,如果太小,事务的日志需要多次切换重做日志文件,并且会频繁的async checkpoint。 |
innodb_log_files_in_group | 每个组里文件数量 |
innodb_mirrored_log_groups | 组的数量 |
innodb_log_group_home_dir | 组的路径 |
二进制日志与InnoDB的重做日志的区别。
二进制日志 | InnoDB的重做日志 |
记录与MySQL有关的日志记录,包括多个存储引擎 | 只记录有关该引擎本身的事务日志。 |
关于一个事务的具体操作内容,即逻辑日志 | 关于每个页的物理情况 |
事务提交前进行提交,只写磁盘一次,无论事务多大 | 事务进行中,不断被写入到重做日志文件 |
重做日志结构:redo_log_type(类型) + space(表空间ID) + page_no(页的偏移量) + redo_log_body(数据部分)
日志缓冲写入磁盘的时机
- 主线程每秒会写入,无论事务是否提交。
- 由innodb_flush_log_at_trx_commit控制,0代表不操作,1代表在执行commit时写入即fsync调用,2代表异步写入到磁盘即写入文件系统的缓存中。