mysql - 删除和插入会为键 PRIMARY 抛出重复条目

标签 mysql database innodb mysql-5.7

我有一个复合 PK,我需要在其中更新其 PK 值之一。但是由于一些内部问题,我无法向 PK 列发出更新查询。

所以我触发 DELETE INSERT 查询。

这个 DELETEINSERT 是在 TRANSACTION (READ COMMITTED) 内完成的。

但有时,当两个请求确实更新到同一行时。 On Duplicate entry for key PRIMARY 错误被抛出。这是随机发生的,我在本地重现此问题时尝试了很多,但找不到根本原因。

笔记:
由于一些内部限制,我无法尝试以下内容。

  • 更新查询到 PK
  • 替换查询
  • 插入重复或插入忽略查询
  • 当前为此表添加运行 ID 将是一次巨大的迁移。

  • 请帮我解决这个问题。

    更新:

    示例表结构:
           Table: temp
    Create Table: CREATE TABLE `temp` (
      `id1` int(11) NOT NULL,
      `id2` int(11) NOT NULL,
      `id3` int(11) NOT NULL,
      `value` int(11) DEFAULT NULL,
      PRIMARY KEY (`id1`,`id2`,`id3`)
    ) ENGINE=InnoDB DEFAULT CHARSET=latin1
    1 row in set (0.00 sec)
    

    样本数据:
    +-----+-----+-----+-------+
    | id1 | id2 | id3 | value |
    +-----+-----+-----+-------+
    |   1 | 100 | 111 |   123 |
    |   2 | 200 | 222 |   456 |
    +-----+-----+-----+-------+
    

    示例查询:

    事务 T1 尝试 to update ID1=3 value where id2=100
    Delete from temp where id2=100
    Insert into temp values(3,100,111,123);
    

    事务 T2 尝试 to update ID1=3 value where id2=100
    Delete from temp where id2=100
    Insert into temp values(3,100,111,123);
    

    删除和插入的次数将始终相同

    最佳答案

    首先,偶尔出现重复的键错误(或例如死锁)从根本上来说没有什么问题。数据库服务器本质上是并发的,如果你不想完全锁定数据库,预计两个进程可能会尝试做一些相互冲突的事情(例如尝试插入同一行)。它被捕获了,你现在的工作就是对此做出正确的 react 。

    对于您的情况,当您没有要删除的行(例如 id 尚不存在)时,您应该会收到错误消息,如果您通常使用删除插入的逻辑,即使对于全新的条目,这种情况尤其可能发生。

    read committed -isolation level 不为不存在的行保留锁(或间隙锁):

    For UPDATE or DELETE statements, InnoDB holds locks only for rows that it updates or deletes. Record locks for nonmatching rows are released after MySQL has evaluated the WHERE condition. This greatly reduces the probability of deadlocks, but they can still happen.



    因此,如果没有要删除的现有行,对于想要插入相同 id 的两个事务 A 和 B,以下流程可能偶尔会发生:
    A: delete id, no rows -> no lock
    B: delete id, no rows -> no lock (and no wait)
    A: insert id -> locks row(s)
    B: insert id -> wait
    A: commit
    B: lock released -> insert id -> duplicate key error
    

    您的详细信息可能会有所不同,您可以使用一些隐藏这些详细信息的 orm,或者您可以例如进行额外的检查以查看该 ID 是否已经存在(但是您应该在交易中包含此检查/选择 for update 以使此检查保持一致),然后使用仅插入的不同程序 - 但在这种情况下,您只会遇到类似的问题:您只会在另一个过程中遇到重复键错误(其中两个“简单”插入相互冲突)。

    最终,您将只需要处理两个 session 尝试同时执行相同操作的情况。由于您排除了在数据库中处理这种情况的所有解决方案(例如 replaceon duplicate key update ),您必须在应用程序中手动捕获和处理它(例如回滚并重复事务)。

    您可以通过将隔离级别更改为 repeatable read 来测试这一点。 (但由于这可能是一个重大变化,您应该只在开发环境中执行此操作)。在这种情况下,这应该会给你一个死锁而不是重复的键错误。虽然这并不能完全解决您的问题,但它可以让您相信这确实是问题所在。

    关于mysql - 删除和插入会为键 PRIMARY 抛出重复条目,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61054660/

    相关文章:

    php - 如何为 Laravel 5 设置数据库? (连接拒绝错误)

    c# - 使用 DbConnection 检索最后插入的记录 - C#

    mysql - 混合 MySQL/非规范化数据存储以优化 REST API 性能

    mysql - MYSQL 如何将一个非常大的表复制到另一个表中?

    sql - 为 SQL Server 中的常用查询创建适当的索引

    python - 从数据库中获取主键(Python/Django)

    mysql - 使用 InnoDB 引擎比较 MySQL 中大 'text' 类型值的最有效方法

    php - 无法完成交易;超过锁等待超时;尝试重新启动事务

    mysql - 使用多个较小的 varchars 与一个大的

    php - 使用php根据时间段拉取mysql数据