防止 MySQL 脏读阻塞显式写锁?

Prevent a MySQL dirty read from blocking an explicit write lock?

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

但是。某些季度和年度报告需要 运行 广泛、非常长的 SELECT 相同 table s,有的只要30秒。当 运行 处理报告时,我 SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED。这似乎有助于缓解报告中防止隐式或行级锁定的许多其他问题。但它们仍然会阻止显式 LOCK TABLES 调用。因此,当这些报告在高峰时段 运行 时,结帐交易可能会在报告 运行 期间冻结。

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

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

的 CREATE TRIGGER

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

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

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

  1. 不要将 LOCK TABLES 与 InnoDB 一起使用。这是一把大锤。正如您所发现的,大锤很危险。
  2. 测试死锁。当一个发生时,重播整个事务。重放可能比等待解锁要快。
  3. 将 transaction_isolation 保留为默认值(可能)。
  4. 建立和维护汇总表。这样,那些 30 多秒的报告将不会花费那么长时间。 http://mysql.rjweb.org/doc.php/summarytables