【问题标题】:Prevent a MySQL dirty read from blocking an explicit write lock?防止 MySQL 脏读阻塞显式写锁?
【发布时间】:2021-06-24 11:34:04
【问题描述】:

我遇到的情况涉及我运行的大型销售点/预订系统。客户拥有在发生购买交易时触发更新的运行余额。也就是说,在存储购买单位的表units 上存在插入、更新和删除触发器,每个触发器将客户的购买和付款相加并更新名为customer_balance 的表。保持这些余额更新而不必通过连接不断地计算它们是必不可少的。业务逻辑还规定,有时可以异步发送与单个客户有关的对units 的多个插入或更新。发生这种情况时,可能会导致死锁,其中两个触发器尝试作用于customer_balance 中的同一行。为了防止这种情况,并且由于您不能将事务放入触发器中,有一些非常快速的更新,我称之为LOCK TABLES units WRITE, customer_balance WRITE,然后购买并让触发器运行,然后立即UNLOCK。这种情况发生得很快且偶尔发生,即使在繁忙时段也不会影响性能。

但是。某些季度和年度报告需要在这些相同的表格上运行大量、非常长的SELECTs,有些长达 30 秒。运行报告时,我SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED。这似乎有助于缓解报告中防止隐式或行级锁定的许多其他问题。但他们仍然阻止明确的LOCK TABLES 调用。因此,当这些报告在高峰时段运行时,结帐交易可能会在报告运行的整个过程中冻结。

我的问题是:除了将隔离级别设置为READ UNCOMMITTED 之外,还有什么方法可以防止长时间运行的SELECT 阻塞显式写锁?

【问题讨论】:

    标签: mysql locking isolation-level


    【解决方案1】:

    LOCK TABLES 获取的表锁是元数据锁。任何查询都会在表上持有元数据锁,并且会阻止其他会话执行 LOCK TABLES,就像它会阻止其他 DDL 语句(如 ALTER TABLE、RENAME TABLE、TRUNCATE TABLE,甚至是引用该表的 CREATE TRIGGER)一样。

    这与事务的 READ UNCOMMITTED 隔离级别无关。在使用 SQL 数据库的 30 年中,我从未发现 READ UNCOMMITTED 的好用处。我建议避免使用该隔离级别。它不会降低锁定要求,只会导致幻读,因为您的查询可能会读取处于更新不完整或可能回滚的状态的数据。

    要解决阻止更新的长时间运行的查询,典型的解决方案是创建一个副本数据库实例,您可以在其中运行长时间的报告查询。即使报告查询阻止了副本上的更新,也没关系,因为它不会阻止源实例上的更新。复制是异步的,所以一旦报告查询完成,更新就会恢复并逐渐赶上。

    另一个常见的解决方案是使用 ETL 将数据移动到数据仓库,由于数据的存储方式,对大型数据集的报表查询无论如何都会更有效。

    【讨论】:

    • 谢谢。如果可能的话,我一直试图避免复制只是为了运行这些报告;设置它只会增加管理工作量和头痛。我弄错了还是 READ UNCOMMITTED 至少阻止了查询阻止其他事务写入?
    • 不。真的不。针对表的任何查询或事务都会导致共享元数据锁。其他会话也可以共享元数据锁,但需要独占元数据锁的会话必须等到没有会话拥有任何类型的元数据锁。
    • 如果你能为我扩展这个,我会很高兴的。如果最低隔离级别读取忽略正在进行的事务,只是在传递每个文件时获得最脏的状态,那么它为什么需要接触元数据或干扰其他任何事情呢?我了解它如何防止全表写入锁,以及它如何导致数据丢失......但它如何干扰其他读取或行级事务?或者,我的意思是,为什么必须这样做?我试图剥离每一行的原始、不完整的版本,无论它是否被写入。那不应该是一个简单的复制操作吗?
    • 如果您正在查询一个表,无论您使用什么隔离级别,您都不希望在查询时更改、重命名或删除该表。因此,您的会话获取共享元数据锁来阻止这些 DDL 操作。并发会话都可以同时查询表,因为它们不会互相阻塞。它们是共享​​>元数据锁。但是任何 DDL 语句都需要一个独占元数据锁,这意味着它必须等到所有会话都完成了表。
    • 你可能会喜欢我的演讲InnoDB Locking Explained with Stick Figures
    【解决方案2】:
    1. 不要将LOCK TABLES 与 InnoDB 一起使用。这是一把大锤。正如您所发现的,大锤很危险。
    2. 测试死锁。当一个发生时,重放整个事务。重播可能比等待解锁更快。
    3. 将 transaction_isolation 保留为默认值(可能)。
    4. 建立和维护汇总表。这样,那些 30 多秒的报告将不会花费那么长时间。 http://mysql.rjweb.org/doc.php/summarytables

    【讨论】:

    • 我在使用它时就知道它是一把大锤,而且它被明智地使用了。重新运行交易不符合要求,因为我们可能有几个不同的查询同时对客户的余额做不同的事情;即使是一次重播也可能会使它们重新陷入僵局。摘要类型表显然是首选解决方案,但我想找到一种方法来处理原始数据,而无需设置收集 cron 进程。该软件中有超过 60 份报告,我不喜欢将它们全部重新链接到副本或摘要。
    • @joshstrike - 对“60 份报告”的评论:每日汇总表可以轻松用于每周、每月和每年;就此而言,任意天数范围。此外,我经常发现 2 或 3 个“报告”可以从一个考虑到 2-3 设计的汇总表中得出。所以,这 60 个可能会缩减到十几个。
    • 对于一些较小的报告,您可能是正确的,但编写的报告生成指令实际上存储在数据库中,作为应用于数据的预期参数和 SQL 语句集。有很多公司希望添加越来越多的报告,并且不希望重构“有效”的东西。还有很多数据来自相同的几个表,但报告以不同的方式使用基于时间的细粒度规则集对其进行聚合,例如过去连续入住超过 5.6 晚和 7 晚的客户数量30、60 和 90 天。
    • 好吧,老板们可以继续使用他们拥有的慢速机制。同时,您可以根据需要重构它们。最终,他们会明白你的价值。当我在这种模式下工作时,有时我可以在一个小时内生成一份新报告——如果我已经有一个接近的汇总表的话。
    • @joshstrike - 5、6、7 和 30、60、90 都来自同一张桌子——当有人离开时,计算他连续住了多少晚。该表将包含(可能)这些列:Date_of 出发日期、num_nights、count_of_users。噗,5,6,7,或其他任何东西,以及 30,60,90,7,365 等等。你给他们一页,让他们插入两个数字。如果那是你 60 人中的 9 人,看看这会走多快?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-04-22
    • 2016-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-14
    • 2020-07-20
    相关资源
    最近更新 更多