专栏首页腾讯数据库技术详解Early Lock Release

详解Early Lock Release

提示:公众号展示代码会自动折行,建议横屏阅读

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

腾讯数据库技术团队对内支持微信红包,彩票、数据银行等集团内部业务,对外为腾讯云提供各种数据库产品,如CDB、CTSDB、CKV、CMongo, 腾讯数据库技术团队专注于增强数据库内核功能,提升数据库性能,保证系统稳定性并解决用户在生产过程中遇到的问题,并对生产环境中遇到的问题及知识进行分享。

var first_sceen__time = (+new Date());if ("" == 1 && document.getElementById('js_content')) { document.getElementById('js_content').addEventListener("selectstart",function(e){ e.preventDefault(); }); } (function(){ if (navigator.userAgent.indexOf("WindowsWechat") != -1){ var link = document.createElement('link'); var head = document.getElementsByTagName('head')[0]; link.rel = 'stylesheet'; link.type = 'text/css'; link.href = "//res.wx.qq.com/mmbizwap/zh_CN/htmledition/style/page/appmsg_new/winwx45ba31.css"; head.appendChild(link); } })();

腾讯数据库技术

赞赏

长按二维码向我转账

受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。

阅读

分享 在看

已同步到看一看

取消 发送

我知道了

朋友会在“发现-看一看”看到你“在看”的内容

确定

已同步到看一看写下你的想法

最多200字,当前共字 发送

已发送

朋友将在看一看看到

确定

写下你的想法...

取消

发布到看一看

确定

最多200字,当前共字

发送中

微信扫一扫 关注该公众号

微信扫一扫 使用小程序

即将打开""小程序

取消 打开

本文分享自微信公众号 - 腾讯数据库技术(gh_83eebc796d5d),作者:腾讯数据库技术

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-05-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • MySQL-8.0 redo优化剖析

    提示:公众号展示代码会自动折行,建议横屏阅读 前言 redo_log的作用设计初衷为了提高写入性能同时解决ACID中Duration。MySQL 8.0对re...

    腾讯数据库技术
  • 减少MySQL主从延迟的神器--并行复制大揭密

    腾讯数据库技术
  • TX-Rocks Sum性能调优之旅

    提示:公众号展示代码会自动折行,建议横屏阅读 TXRocks是TXSQL适配RocksDB的版本,基于Facebook开源的MySQL进行了深度定制和优化。相...

    腾讯数据库技术
  • python网络爬虫:从flicker上爬图片

    分享一个最早接触python时写的一个图片爬虫程序,从flicker上面根据关键字抓取图片,具体流程看代码很容易理解,不过这个程序目前只能抓取第一页的图片,第二...

    the5fire
  • 完美解决Github上下载项目失败或速度太慢的问题

    相中一个项目,然而尝试多次都没办法成功下载,总是在下载到快完成的时候,突然终止。而且有时候下载很慢,可能只有十几k。

    AI算法与图像处理
  • 马斯克不仅承包NASA火箭发射,现在连火箭“摆渡车”都换成特斯拉了

    一辆刷上NASA标识的Model X,正式亮相成为NASA火箭发射“通勤车”,并且由于Model X的翼型门设计,倒也比原来宇航员出车登舱酷炫了许多。

    量子位
  • 7 大最致命的云安全盲点

    当下在提供 IT 服务的方面,云计算正从一个可选项进化为事实上的标准选项。根据“ 2019 年公共云趋势”,从这份企业战略集团(ESG)的报告来看,IaaS 环...

    IT大咖说
  • MySQL Online DDL

    历史上看,MySQL 在 2007 年就完成了在线索引接口的设计。而 MySQL NDB Cluster、TokuDB 都早在 5.1 版本中就支持在线索引添加...

    serena
  • 小白学Flask第五天 | 详解很重要的request对象

    就是 Flask 中表示当前请求的 request 对象,request对象中保存了一次HTTP请求的一切信息。

    Python进击者
  • point inside 点在框内

    如果是矩形比较简单,直接判断四个点的范围,不能推广到多边,考虑到图形的凹凸就更复杂,考虑到程序需要直接拿来用罢了,

    vanguard

扫码关注云+社区

领取腾讯云代金券