我有要求我们需要更新行而不在更新时持有锁。
这是要求的详细信息,我们将每 5 分钟对表运行一次批处理,update blogs set is_visible=1 where some conditions
此查询将在数百万条记录上运行,因此我们不想在更新期间阻止所有行写入。
我完全理解没有写锁的含义,这对我们来说很好,因为 is_visible 列将仅由该批处理更新,其他线程不会更新该列。另一方面,我们不想阻止同一张表的其他列的大量更新
我有要求我们需要更新行而不在更新时持有锁。
这是要求的详细信息,我们将每 5 分钟对表运行一次批处理,update blogs set is_visible=1 where some conditions
此查询将在数百万条记录上运行,因此我们不想在更新期间阻止所有行写入。
我完全理解没有写锁的含义,这对我们来说很好,因为 is_visible 列将仅由该批处理更新,其他线程不会更新该列。另一方面,我们不想阻止同一张表的其他列的大量更新
首先,如果您默认使用 MySQL 的 InnoDB 存储引擎,那么您无法在没有行锁的情况下更新数据,除非通过运行将事务隔离级别设置为 READ UNCOMMITTED
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
但是,我不认为数据库行为是您所期望的,因为在这种情况下允许脏读。READ UNCOMMITTED 在实践中很少有用。
为了补充@Tim 的答案,在 where 子句中使用的列上设置唯一索引确实是一个好主意。但是,请注意,不能绝对保证优化器最终会使用创建的索引选择这样的执行计划。视情况而定,它可能有效,也可能无效。
对于您的情况,您可以做的是将长事务拆分为多个短事务。与其一次更新数百万行,不如每次只扫描数千行会更好。X 锁在每个短事务提交或回滚时释放,为并发更新提供了继续进行的机会。
顺便说一句,我假设您的批次的优先级低于其他在线流程,因此可以将其安排在高峰时间之外,以进一步减少影响。
PS IX 锁不在记录本身上,而是附加到更高粒度的表对象上。并且即使使用 REPEATABLE READ 事务隔离级别,当查询使用唯一索引时也没有间隙锁。
最佳实践是在更新可能与其他事务同时发生时始终获取特定锁。如果您的存储引擎是 MyISAM,那么 MySQL 将在更新期间锁定整个表,而您对此无能为力。如果存储引擎是 InnoDB,那么 MySQL 可能只会对更新的目标记录设置排他 IX 锁,但在这种情况下需要注意。为了实现这一目标,您要做的第一件事是SELECT ... FOR UPDATE
:
SELECT * FROM blogs WHERE <some conditions> FOR UPDATE;
为了确保 InnoDB 只锁定正在更新的记录,需要在WHERE
子句中出现的列上有一个唯一索引。对于您的查询,假设id
所涉及的列必须是主键,否则您需要创建一个唯一索引:
CREATE UNIQUE INDEX idx ON blogs (id);
即使有这样的索引,InnoDB 仍可能在索引值之间的记录上应用间隙锁,以确保REPEATABLE READ
执行合同。
因此,您可以在WHERE
子句中涉及的列上添加索引以优化 InnoDB 上的更新。