数据结构与算法 —— Cache/Buffer 与锁基础 MySQL DBA  彭立勋 Alibaba DBA Team
概要 缓存替换算法 缓冲回写机制 锁机制基础
缓存替换算法 最近最少使用( Least Recently Used , LRU ) 最近最多使用( Most Recently Used , MRU )
LRU 选择近期最少访问的页面作为被替换页,完全按照“最少”实现算法代价高,需要为每个页都配一个计数器,所以一般采用变形,把近期最久未访问过的页作为被替换页,将“多”和“少”变成“有”和“无”。 演化为:最近最久未使用
LRU 举例 序列: 2,3,2,1,5,2,4,5 堆栈变化: 2,X,X  【调进】 2 2,3,X  【调进】 3 2,3,X  【命中】 2 2,3*,1  【调进】 1 2*,5,1  【替换】 5 2,5,1*  【命中】 2 2,5*,4  【替换】 4 2*,5,4  【命中】 5
LRU 命中率 起初堆栈容量增加对命中率从提升很明显,但很快就会减慢这种效果,直到堆栈容量增加对命中率提升毫无效果。
MRU 选择近期最多访问的页面作为被替换页,与 LRU 相反。 适用场景:数据页使用越多越可能不再使用。 例如:连接查询。
MRU 举例 关联查询: TableA  关联  TableB TableA 的数据块一旦用过就可以丢弃,称为“立即丢弃”策略。 TableB 的数据块,被使用越多,会再次使用的可能性就越少,所以,内存不足时,优先替换掉使用次数多的数据块。
Oracle 的缓存替换策略 Oracle 的策略是使用 LRU List ,两端分别是 LRU 端和 MRU 端。
MySQL 的缓存替换策略 将 LRU 队列划分为 hot sub-chain 和 warm sub-chain 。 由 key_cache_division_limit 参数划分,总保持 warm sub-chain 在这个百分比之上。 在 warm sub-chain 中的某个 block 如果被访问次数超过某个值时候,就将该 block 放到 hot sub-chain 的底部。 在 hot sub-chain 中的 block 会随着每一次的 hit 调整位置, hit 越多越接近底部。在顶部停留时间过长就会被降级到 warm sub-chain 的顶部(很可能很快就会被移出 key cache )。
缓冲回写机制 日志记录缓冲区 数据库缓冲区
日志记录缓冲区 由于使用了日志缓冲区,日志记录在输出到磁盘前,可能有一段时间只存在与主存中。 先写日志规则( WAL , Write-Ahead Logging ):在主存中的数据块输出到磁盘前,所有与该数据库中数据有关的日志记录必须已输出到磁盘。
数据库缓冲区 由于数据库采用两层存储结构,系统将数据库存储在磁盘上,需要时将数据调入主存。可能在需要将块 B2 调入内存时,必须覆盖主存中的块 B1 。如果 B1 已经修改过,那么 B1 必须在输入 B2 前输出。 由于 WAL 规则限制,系统将采取: 1. 输出所有与块 B1 有关的日志到磁盘 2. 将块 B1 输出到磁盘(加 Latch ) 3. 将块 B2 由磁盘输入到主存中。
锁机制基础 并发控制 死锁处理 分布式并发控制
并发控制 锁的类型:共享锁( S ),获得锁可读不可写。排它锁( X ),获得锁可读可写。 共享锁与共享锁相容,排它锁与任何锁不相容。
两阶段封锁协议 增长阶段( Growing Phase ):事务可以获得锁,但不能释放锁。 缩减阶段( Shrinking Phase ):事务可以释放锁,但不能获得新锁。 一开始,事务处于增长阶段,事务根据需要获得锁。一旦该事务释放锁,就进入缩减阶段,不能再发出加锁请求。 两阶段封锁协议不保证不死锁,只保证可串行化。
死锁处理 死锁预防 死锁检测 死锁恢复
死锁预防 要求按一个统一的顺序依次获取资源,不得跨越顺序获取资源。(只会出现等待,不会出现相互死锁 ) Wait-Die 机制(非抢占):假设事务 Ti 申请数据项被 Tj 持有,只有 Ti 比 Tj 早开始时等待,否则回滚。 Wound-Wait 机制(抢占):假设事务 Ti 申请的数据项被 Tj 持有,只有当 Ti 比 Tj 后开始时,允许 Ti 等待,否则 Tj 回滚。 超时机制。
死锁检测 等待图。当事务 Ti 申请的数据被 Tj 持有时,边 Ti->Tj 插入等待图中。当且仅当等待图中包含环时,存在死锁。 环检测算法:深度遍历、广度遍历,检测生成树的以遍历节点集是否有重复。
死锁恢复 选择牺牲者:回滚“代价最小”的事务。 回滚:部分回滚、全部回滚。 饿死:必须保证一个事务被选为牺牲者的次数有限且较少,避免饿死。
分布式并发控制 两阶段提交协议 Xa 事务标准 在第一阶段,交易中间件请求所有相关数据库准备提交(预提交)各自的事务分支,以确认是否所有相关数据库都可以提交各自的事务分支。 在第二阶段,交易中间件审查所有数据库返回的预提交结果,如所有数据库都可以提交,交易中间件将要求所有数据库做正式提交,这样该全局事务被提交。

Database.Cache&Buffer&Lock