mysql体系结构
- 连接池组件
- 管理服务和工具组件
- SQL接口组件
- 查询分析器组件
- 缓冲组件
- 插件式存储引擎
- 物理文件

InnoDB特点概述
- 主要面向
OLTP - 通过使用
MVCC来获得高并发性, 提供一致性非锁定读, 并实现了SQL标准的4中隔离级别 - 支持行锁
- 使用
next-key locking的策略来避免幻读(phantom)现象 - 提供了插入缓冲(
insert buffer)、二次写(double write)、自适应哈希索引(adptive hash index)、预读(read ahead)等高性能和高可用功能
InnoDB体系结构
InnoDB线程:
InnoDB内存:
Master Thread
IO Thread
主要负责IO请求的回调处理, 有4类IO Thread:
write: 默认innodb_write_io_threads = 4, 即默认是4线程read: 默认innodb_read_io_threads = 4insert buffer threadlog IO thread
Purge Thread
从InnoDB 1.1 版本开始, purge操作可以独立到单独的线程中进行。用于回收undo页。
Page Cleaner Thread
在InnoDB 1.2版本中引入的(之前在Master Thread中)。用于脏页的刷新操作。
缓冲池(Buffer Pool)
缓冲池的设计目的是为了协调CPU速度与磁盘速度的鸿沟。
把磁盘上的数据加载到缓冲池,避免每次访问都进行磁盘IO,起到加速访问的作用。
内部组成
- Buffer Pool中默认的缓存页大小和在磁盘上默认的页大小是一样的,都是16KB
- 为了管理Pool中的缓存页,Innodb为每一个缓存页创建了一些所谓的控制信息。–控制信息也是写在页上面的。
- 控制信息包括该页所属的表空间编号、页号、缓存页在Buffer Pool中的地址、链表节点信息、一些锁信息以及LSN信息。
- 因为每个缓存页对应的控制信息占用的内存大小是相同的,因此从buffer pool中分配一块内存专门记录控制信息–控制块
- 控制块和缓存页是一一对应的,它们都被存放到 Buffer Pool 中,其中控制块被存放到 Buffer Pool 的前边,缓存页被存放到 Buffer Pool 后边
- 控制块和缓存页之间是有碎片的–当然如果大小分配合理也有可能没有碎片。
- 每个控制块大约占用缓存页大小的5%,innodb_buffer_pool_size设置的大小并不包含控制块的大小,也就是说InnoDB在为Buffer Pool向操作系统申请连续的内存空间时,这片连续的内存空间一般会比innodb_buffer_pool_size的值大5%左右
时延参考数据 :
Latency Comparison Numbers (~2012)
----------------------------------
L1 cache reference 0.5 ns
Branch mispredict 5 ns
L2 cache reference 7 ns 14x L1 cache
Mutex lock/unlock 25 ns
Main memory reference 100 ns 20x L2 cache, 200x L1 cache
Compress 1K bytes with Zippy 3,000 ns 3 us
Send 1K bytes over 1 Gbps network 10,000 ns 10 us
Read 4K randomly from SSD* 150,000 ns 150 us ~1GB/sec SSD
Read 1 MB sequentially from memory 250,000 ns 250 us
Round trip within same datacenter 500,000 ns 500 us
Read 1 MB sequentially from SSD* 1,000,000 ns 1,000 us 1 ms ~1GB/sec SSD, 4X memory
Disk seek 10,000,000 ns 10,000 us 10 ms 20x datacenter roundtrip
Read 1 MB sequentially from disk 20,000,000 ns 20,000 us 20 ms 80x memory, 20X SSD
Send packet CA->Netherlands->CA 150,000,000 ns 150,000 us 150 ms
Notes
-----
1 ns = 10^-9 seconds
1 us = 10^-6 seconds = 1,000 ns
1 ms = 10^-3 seconds = 1,000 us = 1,000,000 ns
缓冲池对读写操作的关系
- 读: 判断要读取的页是否在缓冲池中, 如果不在则会将页加载到缓冲池中, 然后再从缓冲池中读取。
- 写: 对于数据库中页的修改操作, 首先修改在缓冲池中的页, 然后再通过
CheckPoint机制刷新回磁盘上。
缓冲池中的数据页类型
- 索引页
- 数据页
- undo页
- 插入缓冲(
insert buffer) - 自适应哈希索引(
adaptive hash index) - InnoDB存储的锁信息
- 数据字典信息(
data dictionary)
LRU List
InnoDB存储引擎中, 缓冲池中页的大小默认为16KB, 是通过使用LRU算法来进行管理的。
InnoDB存储引擎对传统的LRU算法做了midpoint insertion strategy优化, 不会直接插入到列表的头部, 而是会插入到midpoint位置。默认情况下该位置在LRU列表的5/8处(63%)。
midpoint insertion strategy 可以防止热点数据被移出LRU列表。比如全表扫描的时候, 很可能新的页并不会频繁使用, 显然这些页替换调LRU的热点数据是不明智的。
Free List
LRU List新增节点的时候, 会先从Free list申请, 如果Free list里没有可用的空闲页, 那么LRU list将淘汰末尾的页, 然后将该也空间分配给新的页。
Flush List
LRU list列表中的页被修改后, 这些脏页就会被维护到Flush list中, 数据库会根据CheckPoint机制将脏页刷新回磁盘。
重做日志缓冲(redo log buffer)
默认大小为innodb_log_buffer_size = 838869, 即 8MB。
通常情况下, 8MB足以满足大部分的应用。因为重做日志在下列三种情况会刷到外部磁盘的重做日志文件中:
- Master Thread每秒会将
redo log buffer刷到redo log file中 - 每个事物提交时会将
redo log buffer刷到redo log file中 - 当重做日志缓冲池剩余空间小于1/2时会将
redo log buffer刷到redo log file中
额外的内存池
对一些数据结构本身的内存进行分配时, 需要重额外的内存池进行申请。例如缓冲池中的帧缓冲(frame buffer)还有缓冲控制对象(buffer control block)等。
Checkpoint
InnoDB存储引擎内部, 有两种Checkpoint, 分别是Sharp Checkpoint, Fuzzy Checkpoint。Checkpoint技术的目的是解决一下几个问题:
- 缩短数据库的恢复时间: 当数据库发生宕机时, 数据库不需要重做所有的日志, 只需对
Checkpoint之后的重做日志进行恢复, 这样就大大缩短了恢复的时间。 - 缓冲池不够时, 将脏页刷新到磁盘: 当缓冲池不够用时, 根据LRU算法会淘汰掉一些页, 若淘汰的页为脏页, 那么需要强制执行
CheckPoint, 将脏页刷回磁盘。 - 重做日志不可用时, 刷新脏页: 重做日志的空间是有限的, 是循环使用的。当被覆盖的时候, 需要强制执行
Checkpoint将脏页刷新到磁盘。
Sharp Checkpoint
Sharp Checkpoint会将所有的脏页刷新回磁盘。发生在数据库关闭的时候。
Fuzzy Checkpoint
InnoDB存储引擎内部使用Fuzzy Checkpoint进行页的刷新, 即只刷新一部分脏页。分为以下几种情况:
- Master Thread Checkpoint: 在
Master Thread的flush loop中, 每秒或每十秒触发。 - FLUSH_LRU_LIST checkpoint:
Page Cleaner线程会检查LRU列表中是否有足够的可用空闲页, 默认为innodb_lru_scan_depth = 1024。倘若如果没有1024个可用空闲页, 那么会将LRU列表尾端的页移除, 如果这些页中有脏页, 那么需要进行Checkpoint - Async/Sync Flush Checkpoint: 当重做日志文件不可用的时候, 会触发。主要是为了保证重做日志的循环使用。
- Dirty Page too much Checkpoint: 当脏页数太多的时候, 会导致InnoDB存储引擎强制进行
Checkpoint. 默认是当超过innodb_max_dirty_pages_pct = 75的时候, 即脏页数量占据75%的时候, 会强制进行Checkpoint
InnoDB关键特性
插入缓冲(Insert Buffer)
目的: 为了提升辅助索引的插入性能
插入缓冲的作用
辅助索引的插入是较为离散的, 为了避免更新辅助索引的时候要频繁地离散读取数据,
所以InnoDB存储引擎设计了Insert Buffer, 对于非聚集索引的插入或更新操作, 不是每次都直接插入到索引页中,
而是先判断插入的非聚集索引页是否在缓冲池中, 若存在, 则直接插入;
若不存在, 那么先放入到一个Insert Buffer对象中, 假装已经插入到叶子节点, 再以一定的频率合并(merge)到辅助索引叶子节点中。
插入缓冲的缺点
- 如果数据库发生了宕机, 有大量的
Insert Buffer没有合并到实际的非聚集索引中去, 那么此时恢复可能需要很长的时间, 极端情况下甚至要几个小时。 - 在写密集的情况下, 插入缓冲会占用过多的缓冲池内存(innodb_buffer_pool), 默认最大可以占用到1/2的缓冲池内存。
使用插入缓冲需要满足的条件
Insert Buffer的使用需要同时满足一下两个条件:
- 索引是辅助索引(secondary index)
- 索引不是唯一(unique)的
唯一索引为什么不能使用插入缓冲
为什么不能是唯一的? 如果要校验唯一性, 那么就还是需要进行读取加载对应的数据页才能进行判断。
变更缓冲(Change buffer)
InnoDB从1.0.x版本开始引入了Change Buffer, 可以看做是Insert Buffer的升级版, 支持INSERT、DELETE、UPDATE。
InnoDB1.2.x版本开始可以通过参数innodb_change_buffer_max_size来控制Change Buffer最大使用内存的数量, 默认值为25, 表示最多使用1/4的缓冲池内存空间。
Insert/Change Buffer的实现
Insert/Change Buffer的数据结构是一颗B+树, 且全局只有一颗Insert/Change Buffer B+树。
什么时候合并(Merge) Insert Buffer
- 辅助索引页被读取到缓冲池时: 例如在执行SELECT查询时, 此时需要将辅助索引页读取到缓冲池中, 此时需要检查
Insert Buffer Bitmap页, 确认该索引是否有记录存放于Insert Buffer B+树中。如果有, 则将Insert Buffer B+树中的记录插入到该辅助索引页中。 Insert Buffer Bitmap页追踪到该辅助索引页已无可用空间时:Insert Buffer Bitmap里会记录可用空间, 若检测到插入记录后不够1/32页的剩余空间, 则会强制进行一次合并操作。Master Thread: 每秒或每10秒进行一次Merge Insert Buffer的操作。会随机地选择Insert Buffer B+树中连续的几页, 进行merge。
两次写(double write)
目的: 提供数据页的可靠性
可能某个16KB的页只写了前4KB时, 数据库发生了宕机, 这种情况被称为部分写失效(partial page write)。
自适应哈希索引(Adaptive Hash Index, AHI)
异步IO
刷新邻接页
Mysql数据库的文件
InnoDB存储引擎表的文件
InnoDB存储引擎表的逻辑存储及实现
索引
索引下推优化
锁
MySQL表级锁
MySQL里面表级别的锁有两种:
- 表锁: InnoDB支持行锁, 所以一般不使用
lock tables命令来控制并发。 - 元数据锁(meta data lock, MDL): 公平的读写锁。 不需要显示使用, 在访问一个表的时候会被自动加上。MDL的作用是保证读写的正确性, 防止查询的过程中表结构发生变更。
事务
read view
binlog
redo log
undo log
分布式事务
主从复制
主从复制的步骤
master把数据更改记录到binlog中slave把主服务器的binlog复制到自己的中继日志(relay log)中slave重做中继日志中的日志, 把更改应用到自己的数据库上, 以达到最终一致性。
主从复制工作原理
slave的I/O线程负责读取master的binlog, 并将其保存为relay logslave的SQL线程负责执行中继日志

分库分表
为什么要分库分表
- 大量请求阻塞: 在高并发场景下,大量请求都需要操作数据库,导致连接数不够了,请求处于阻塞状态。
- 存储出现问题: 业务量剧增,单库数据量越来越大,给存储造成巨大压力。
分表
分表:是为了解决由于单张表数据量多大,而导致查询慢的问题。大致三、四千万行数据就得拆分,不过具体还是得看每一行的数据量大小,有些字段都很小的可能支持更多行数,有些字段大的可能一千万就顶不住了。
分库
分库:是为了解决服务器资源受单机限制,顶不住高并发访问的问题,把请求分配到多台服务器上,降低服务器压力。
垂直拆分
hash路由
大众点评订单
通过UserId后四位mod 32分到32个库中,同时再将UserId后四位Div 32 Mod 32将每个库分为32个表
按2^n拆分(类比HashMap里的2^n。对比一下一致性哈希。)
路由表

将ID和库的Mapping关系记录在一个单独的库中。
优点:ID和库的Mapping算法可以随意更改。
缺点:引入额外的单点。
范围路由
比如按照时间区间或ID区间来切分。
优点:单表大小可控,天然水平扩展。不需要做数据迁移
缺点:有热点问题, 一段时间的数据会集中到一张表上。无法解决集中写入瓶颈的问题。
range+hash
微信红包场景可用

snowflake模式下要注意, 每毫秒第一个id要随机生成, 例如leaf, 否则hash分库会不均匀。
分库分表带来的复杂性
跨库关联查询
在单库未拆分表之前,我们可以很方便使用 join 操作关联多张表查询数据,但是经过分库分表后两张表可能都不在一个数据库中,如何使用 join 呢?
有几种方案可以解决:
- 字段冗余:把需要关联的字段放入主表中,避免 join 操作;
- 数据抽象:通过ETL等将数据汇合聚集,生成新的表;
- 全局表:比如一些基础表可以在每个数据库中都放一份;
- 应用层组装:将基础数据查出来,通过应用程序计算组装;
分布式事务
单数据库可以用本地事务搞定,使用多数据库就只能通过分布式事务解决了。
常用解决方案有:基于可靠消息(MQ)的解决方案、两阶段事务提交、柔性事务等。
排序、分页、函数计算问题
在使用 SQL 时 order by, limit 等关键字需要特殊处理,一般来说采用分片的思路:
先在每个分片上执行相应的函数,然后将各个分片的结果集进行汇总和再次计算,最终得到结果。
分布式 ID
如果使用 Mysql 数据库在单库单表可以使用 id 自增作为主键,分库分表了之后就不行了,会出现id 重复。
常用的分布式 ID 解决方案有:
- UUID
- 基于数据库自增单独维护一张 ID表
- 号段模式
- Redis
- 雪花算法(Snowflake)
- 百度uid-generator
- 美团Leaf
- 滴滴Tinyid
多数据源
分库分表之后可能会面临从多个数据库或多个子表中获取数据,一般的解决思路有:客户端适配和代理层适配。
业界常用的中间件有:
- shardingsphere(前身 sharding-jdbc)
- Mycat
参考文章
- cmu讲lock/latch 例子比较详细
- 数据库事务原子性、一致性是怎样实现的?
- 《Mysql技术内幕 InnoDB存储引擎》
- InnoDB关键特性之double write
- InnoDB 重要特性 Double Write 实现原理
- mysql 为何需要Double Write?有redo log还不够吗?
- MySQL:数据库宕机以后恢复的过程?如何保证事务的ACID特性?
- On learning InnoDB: A journey to the core
- innodb_support_xa的作用
- MySQL学习(二)索引与锁
- MySQL 8.0 Reference Manual-InnoDB Locking
- Innodb行锁源码学习(一)
- MySQL Innodb行锁剖析
- MySQL 加锁处理分析
- 分库分表?如何做到永不迁移数据和避免热点?
- MySQL · 源码分析 · InnoDB的read view,回滚段和purge过程简介
- MySQL总结–MVCC(read view和undo log)
- 从ReadView深入理解MySql MVCC原理
- MVCC多版本并发控制
- mysql Innodb_buffer_pool的原理
- MySQL幻读
- InnoDB MVCC何时创建read view
- 美团DB数据同步到数据仓库的架构与实践
- 面试题:我们为什么要分库分表?
- 数据库分库分表事务解决方案
- MTDDL——美团点评分布式数据访问层中间件
- 大众点评订单系统分库分表实践
- mysql分表的3种方法
- MySQL分库分表方案
- 在面试时被问到,为什么MySQL数据库数据量大了要进行分库分表?
- 数据库分库分表解决方案汇总
- MYSQL单表数据达2000万性能严重下降,为什么?
- 一次分表踩坑实践的探讨
- 分表后需要注意的二三事
- 一次难得的分库分表实践
- 数据库分库分表基础和实践
- 数据库分库分表思路 (1)(数据库分区、分表、分库、分片)
- innodb源码分析
- Mysql数据库常用分库和分表方式
- MySQL索引前世今生
- MySQL 2PC & Group Commit
- 深入学习MySQL事务:ACID特性的实现原理
- MySQL · 引擎特性 · InnoDB redo log漫游
- MySQL · 引擎特性 · InnoDB undo log 漫游
- MySQL · 引擎特性 · InnoDB 崩溃恢复过程
https://zhuanlan.zhihu.com/p/341317422
https://zhuanlan.zhihu.com/p/343226202
http://kernelmaker.github.io/InnoDB_redo_log
http://mysql.taobao.org/monthly/
https://toutiao.io/posts/2cvy58/preview
https://tech.meituan.com/2016/11/18/dianping-order-db-sharding.html