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 = 4
insert buffer thread
log 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 log
slave
的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