在 MariaDB/MySQL 中不加锁地删除?`(InnoDB)

Posted

技术标签:

【中文标题】在 MariaDB/MySQL 中不加锁地删除?`(InnoDB)【英文标题】:DELETE without lock in MariaDB/MySQL?`(InnoDB) 【发布时间】:2017-10-20 19:55:39 【问题描述】:

据我所知,DELETE FROM ... WHERE 在桌子上发出锁。

现在,我在 MariaDB 中有一个巨大的 InnoDB 表,其大小为 1TB+,正在使用中,并且在搜索整个数据库以删除要删除的行之前将其锁定是没有选择的。

有什么方法可以删除符合某些条件的行而不在删除时锁定它?

以下是本案例的更多细节:

服务器正在运行来自 Debian Stretch 存储库的 MariaDB 10.1.22-3 服务器有 32 GB 内存和innodb_buffer_pool_size = 20G 数据库大小为 1TB+,随时都有大量活动的 INSERTs 和 SELECTs 整个数据库只包含2个表: 实际数据的一个表(它基本上具有类似data (BIGINT id, LONGTEXT data) 的结构(其中data 是一大块JSON。我知道这不是一个完美的关系数据库模型,但JSON 来自第三个方,它非常复杂,并且可能随时包含来自第三方的结构更改,恕不另行通知) 还有一个表用于某种“索引”以满足SELECTs。 (简化的例子,这可能有类似data_index (BIGINT id, INT userId, INT itemId, BIGINT timestamp) 的结构,所以我可以在userId 和itemId 上使用SELECT,并加入实际数据。(时间戳是unix 时间戳,以毫秒为单位) 就像我说的,数据只能在有限的时间内存储。所以基本上现在我想创建一个每天运行一次的 cronjob 来删除超过 7 天的行。

为了完成任务,我自然会想出这个简单的查询:

DELETE `data`, `data_index`
FROM `data_index`
LEFT JOIN `data` ON `data`.`id` = `data_index`.`id`
WHERE `timestamp` > (NOW() * 1000) - (7 * 24 * 60 * 60 * 1000)

但这可能会锁定表很长时间。我如何在不锁定表的情况下完成相同的任务,以便数据库对其他SELECTINSERT 查询仍然有效?

【问题讨论】:

我用了截断,成功了!! 【参考方案1】:

不,如果不锁定检查的行,您将无法删除。

但是您可以通过在您正在搜索的timestamp 列上创建索引来最小化检查的行数。

这还将针对您可能尝试在表末尾插入的潜在行创建 gap locks,以确保新行不会影响 DELETE。

在 InnoDB 中,像 DELETE 创建的普通写锁不会阻塞读取。并发事务仍然可以读取行——甚至是您正在删除的行。

普通的写锁不会锁定整个表。好吧,它使用intention lock 锁定表,这只是防止其他表锁定,例如 ALTER TABLE 或 DROP TABLE 所需的锁定。换句话说,当表正在进行任何读取或写入时,您不能 ALTER/DROP 表。

您可能会喜欢我的演示文稿:InnoDB Locking Explained with Stick Figures。

【讨论】:

【参考方案2】:

(NOW() * 1000) - (7 * 24 * 60 * 60 * 1000) 看起来不像是有效时间。它是20170519568613000,看起来像是DATETIME 和某种毫秒的混合体。 也许你想要UNIX_TIMESTAMP() * 1000 - (7 * 24 * 60 * 60 * 1000) = 1494742589000

您希望删除多少行?如果是大数,那就考虑分区,或者deleting in chunks

【讨论】:

哎呀,没错,使用了错误的时间功能。我会看看分区和那篇文章,谢谢:) 那篇文章链接到我关于分区的文章。 (通常我不赞成分区,但如果您有“时间序列”并定期删除“旧”行,那么分区非常好。)【参考方案3】:

也许我错了,但在https://dev.mysql.com/doc/refman/5.7/en/innodb-locks-set.html 上我读到它会生成行锁,而不是表锁。

你可以试试

DELETE ... FROM ... WHERE ... 限制x

并根据需要执行多次。在执行之间,其他查询可以进入并最小化影响。当然,在低负载时间完成这项工作。

【讨论】:

那么它仍然会阻止所有尝试选择这些行的选择,不是吗? 是的,确实如此。这是因为我使用限制进行多次删除。一点点删除需要一些时间,并且会产生一些待处理的查询队列。这在生产环境中是可行的,特别是在低负载时间。有 6000 万行表发生在我身上,我就是这样做的。 这很有趣。但是,我不能只使用 SELECT 来获取 id,然后循环 id 以将它们一一删除吗? 是的,但是这样可以节省选择 ID 成本,并且不能修改查询过滤器。您执行相同且最简单的查询,以确保没有更多要删除的行。它也适用于更新,对我来说效果很好。

以上是关于在 MariaDB/MySQL 中不加锁地删除?`(InnoDB)的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程之对写业务加锁,对读业务不加锁,会产生脏读

MariaDB(MySQL)创建删除选择及数据类型使用详解

条件更新 MariaDB (MySQL)

T-SQL 为数据库中的所有表创建一个不加锁的视图

美团终面:你确定CAS不加锁吗?

美团终面:你确定CAS不加锁吗?