内容简介:Early Lock Release 的原理数据库领域存在很多优化措施(例如 group commit),它们很早就被提出来了,ELR 也不例外[在数据库中,典型的一个事务 t1 操作流程如下:
提示:公众号展示代码会自动折行,建议横屏阅读
Early Lock Release 的原理
数据库领域存在很多优化措施(例如 group commit),它们很早就被提出来了,ELR 也不例外[ 1] 。在多核处理器的时代,ELR 又被人发掘出来评估对系统性能的影响,例如[ 2] 和[ 3] 评估了包括 ELR 在内的多种性能优化措施。
在数据库中,典型的一个事务 t1 操作流程如下:
0. begin transaction;
1. search & lock tuple for update;
2. update tuple & generate log;
3. write commit log;
4. flush logs;
5. unlock tuples;
6. commit & notify user;
容易发现,事务在写出并且持久化日志的时候会带来 IO,而 IO 通常而言比较耗时。如果事务中此前的操作都是内存操作的话(在内存数据库或 LSM 结构的系统中更明显),这些 IO 的相对耗时就会显得更大。IO 虽然可以做成异步的,但是在 IO 结束之前锁都仍然会被持有,从而会阻塞其他的并发事务。如果可以把第 5 步释放锁的操作提前,放到第 4 步刷出日志之前,则可以让并发操作同一记录(试图获得锁)的事务 t2 提前开始执行,从而可以增加系统的吞吐量。显然,第 6 步的 commit 操作不能提前,仍然必须等到日志持久化完成之后才能通知用户提交成功,因此用户事务的响应时间不会缩短。
unlock 提前会带来什么副作用吗?此时 t2 可以读取并修改 tuple 的内容,从数据依赖的角度看,t2 依赖了 t1。万一 t1 在提交的过程中(flush log 等操作)失败了,导致事务回滚,t2 也必须回滚(可以称之为:级联回滚)。更进一步,t2 此前读到的 t1 的数据实际上是脏数据(读到了未提交的数据,并且还在其上做了更新)。如果业务逻辑本来就是要读并且更新(例如在电商秒杀业务中,做减库存这样的热点行更新操作),脏读并不会造成实质上的不良后果。如果脏读并写了一条记录到另一个持久化的系统(例如消息队列中),则可能会带来业务上的副作用。
导致提交失败最可能的原因是写日志失败,例如日志盘满或坏了。随着云计算的流行,网络盘的使用非常普遍,写盘失败的概率可以认为是相对增加了。如果把数据库存在可拔插盘上(例如移动盘),拔插移动盘也容易触发写盘失败。如果日志文件设计成只有一个并确保总是按照 LSN 从小到大的顺序写出,在 t1 的日志写盘失败的时候,t2 必定也会失败。只不过在内存中事务已经提交了,对某些系统而言,要做回滚操作可能会很复杂或难以实现。最简单的策略反而是让系统自动 crash,系统重启的时候自然会去走崩溃恢复的逻辑。不过,提交失败的具体原因比较多,可能是盘满了、也可能是盘坏了等,需要针对性的处理,虽然要处理得很完善不那么容易[ 4] 、[ 5] 。
MySQL 作为最流行的开源数据库,自然不会放过这样一个经典的优化。下面就让我们来看一看它做了哪些尝试。
Server 层面的 ELR
Facebook 曾经在 MySQL 的 Server 层做过一个 ELR 的实现[ 6] ,来改进高并发场景下的热点行更新的系统性能。不过这个实现在被 port 到 MariaDB 后发现存在致命的缺陷[ 7] ,最后导致代码被回滚掉了。这个改进虽然失败了,但是其中的思考和经验教训 8 对我们理解 MySQL 以及 ELR 还是很有价值的。
众所周知,MySQL 在支持 binlog 的时候,事务提交要走一个内部的分布式事务(XA),执行 prepare()、写 binlog、commit() 三个步骤。这个改进试图把锁的释放提前到 prepare() 阶段,从而提高性能。按照前面一节的描述,实现时需要保证事务 t1、t2 等有依赖关系的事务之间的提交顺序,也需要保证在异常恢复时它们之间的顺序。
在事务 t1 提交失败时,mysqld 可以自杀。但是在异常恢复时,对于 prepared 但是没有 committed 事务,InnoDB 层是按照事务 ID 从大到小的顺序恢复的。而事务 ID 的分配并不等价于事务之间的依赖关系(事务 ID 可以在事务开始或首次进行写操作时分配),因此恢复的时候,事务的先后顺序可能会被搞反,从而导致数据错乱。如果不修改系统来记录事务的依赖关系,并因此修改对应的事务恢复逻辑,这个 Bug 就无法回避。考虑到修改的代价以及数据正确比性能更重要,这最终导致了 patch 被回滚。
InnoDB 层面的 ELR
下面以 MySQL 5.7.20 为例,分析一下 InnoDB 中事务提交时的基本调用流程。事务提交的主要函数为 trx0trx.cc 中的 trx_commit() 函数,它调用了 trx_commit_low(),后者又调用了 trx_commit_in_memory(),它们之间是一个线性关系,比较直接。
trx_commit() -> trx_commit_low() -> trx_commit_in_memory()
而 trx_commit_in_memory() 会先调用 lock_trx_release_locks(),再调用 trx_flush_log_if_needed()。(注,在 trx->flush_log_later 为 true 时,写日志的时机则还要往后一些。)
trx_commit_in_memory() -> lock_trx_release_locks(), trx_flush_log_if_need()
事务在内存中提交的时候,按照 WAL 的原则应该是先持久化日志,再释放锁。但是这里却是先释放锁,再刷日志。在 lock0lock.cc, lock_trx_release_locks() 函数中有一段有趣的注释:
/* The following assignment makes the transaction committed in memory and makes its changes to data visible to other transactions. NOTE that there is a small discrepancy from the strict formal visibility rules here: a human user of the database can see modifications made by another transaction T even before the necessary log segment has been flushed to the disk. If the database happens to crash before the flush, the user has seen modifications from T which will never be a committed transaction. However, any transaction T2 which sees the modifications of the committing transaction T, and which also itself makes modifications to the database, will get an lsn larger than the committing transaction T. In the case where the log flush fails, and T never gets committed, also T2 will never get committed. */ /*--------------------------------------*/ trx->state = TRX_STATE_COMMITTED_IN_MEMORY; /*--------------------------------------*/
注释说的就是我们在前面提到的脏读问题。那么 InnoDB 是怎么处理日志提交失败(这里只看写出日志失败)的呢?调用序列如下:
trx_flush_log_if_needed() -> trx_flush_log_if_needed_low() -> log_write_up_to() -> log_write_flush_to_disk_low() -> fil_flush() -> os_file_fsync_posix()
可以看到, fil_flush() 最后会走到 os_file_fsync_posix()(这里以 Linux 为例)。最后这个函数是 fsync 的包装,而如果 fsync 失败了(例如遇到 EIO 等错误),它会 sleep,然后重试。而由于 fsync 本身实现的问题[ 5] ,重试后的 fsync 会成功返回。所以,写日志失败并不会立即 kill 掉 mysqld,因此可能会导致数据错误。后来的 MySQL 版本也已经修复了这个问题。
ELR 对性能的影响
从原理上看,ELR 可以改进系统的性能。参考文献[ 2] 、[ 3] 则给出了具体的测试验证。Facebook 也是因为性能才尝试在 Server 层做出改进。如果仅测试 InnoDB,它本身的实现也对性能有改善。可是在 MySQL 中因为对 binlog 的支持引入了内部的 XA 机制,将这些优化的效果给掩盖掉了。
References
1 Implementation Techniques for Main Memory Database Systems. https://15721.courses.cs.cmu.edu/spring2016/papers/p1-dewitt.pdf 2 Aether: A Scalable Approach to Logging. http://www.cs.albany.edu/~jhh/courses/readings/johnson.vldb10.logging.pdf 3 Improving OLTP concurrency through Early Lock Release. https://infoscience.epfl.ch//record/152158/files/ELR-single-column.pdf 4 PostgreSQL's fsync() surprise. https://lwn.net/Articles/752063/ 5 PostgreSQL's handling of fsync() errors is unsafe and risks data loss at least on XFS. https://www.postgresql.org/message-id/flat/CAMsr%2BYHh%2B5Oq4xziwwoEfhoTZgr07vdGG%2Bhu%3D1adXx59aTeaoQ%40mail.gmail.com#CAMsr+YHh+5Oq4xziwwoEfhoTZgr07vdGG+hu=1adXx59aTeaoQ@mail.gmail.com 6 Implement release of row locks in InnoDB during prepare() phase. http://worklog.askmonty.org/worklog/Server-Sprint/?tid=163 7 –innodb-release-locks-early=1 breaks InnoDB crash recovery. https://bugs.launchpad.net/maria/+bug/798213 8 Tale of a Bug. https://kristiannielsen.livejournal.com/15893.html
腾 讯 数 据 库 技 术 团 队 对 内 支 持 微 信 红 包 , 彩 票 、 数 据 银 行 等 集 团 内 部 业 务 , 对 外 为 腾 讯 云 提 供 各 种 数 据 库 产 品 , 如 C D B 、 C T S D B 、 C K V 、 C M o n g o , 腾 讯 数 据 库 技 术 团 队 专 注 于 增 强 数 据 库 内 核 功 能 , 提 升 数 据 库 性 能 , 保 证 系 统 稳 定 性 并 解 决 用 户 在 生 产 过 程 中 遇 到 的 问 题 , 并 对 生 产 环 境 中 遇 到 的 问 题 及 知 识 进 行 分 享 。
以上所述就是小编给大家介绍的《详解Early Lock Release》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Flutter 完整开发实战详解(十六、详解自定义布局实战)
- 数据结构 1 线性表详解 链表、 栈 、 队列 结合JAVA 详解
- 详解Openstack环境准备
- Java泛型详解
- iOS RunLoop 详解
- Raft协议详解
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Everything Store
Brad Stone / Little, Brown and Company / 2013-10-22 / USD 28.00
The definitive story of Amazon.com, one of the most successful companies in the world, and of its driven, brilliant founder, Jeff Bezos. Amazon.com started off delivering books through the mail. Bu......一起来看看 《The Everything Store》 这本书的介绍吧!
JSON 在线解析
在线 JSON 格式化工具
Base64 编码/解码
Base64 编码/解码