【问题标题】:Lock SQL table, then perform transaction, for inserting conditional on select value锁定 SQL 表,然后执行事务,用于根据选择值插入条件
【发布时间】:2012-08-10 16:25:26
【问题描述】:

我需要完成以下工作:

我需要在同一个表中插入两行,要么都插入,要么都不插入(原子插入两行)。

我使用 InnoDB 表上的事务来执行此操作,如下所示:

$db->beginTransaction();

# Using PDO prepared statments, execute the following queries:
INSERT INTO t1 set uid=42, foo=1
INSERT INTO t1 set uid=42, foo=2

$db->commit();

但是,如果表中没有列的值为“42”的行,我也只希望插入这些行。

所以我这样做:

$stmt = $db->prepare("SELECT id FROM t1 WHERE uid != ?");
$stmt->execute(array(42));
if($stmt->fetchColumn() < 0){
    # There is no row with uid=42, so
    # perform insertions here as per above.
    INSERT INTO t1 set uid=42, foo=1
    INSERT INTO t1 set uid=42, foo=2
}

但是这里存在竞争条件,因为 uid=42 的行可以在检查该行之后立即插入,并且在插入新行之前插入。

我应该如何解决这个问题?

我可以锁定表,然后在表锁内执行 InnoDB 事务吗?

我可以在事务内部进行选择以检查 uid=42 的现有行吗?那是没有竞争条件的吗?

【问题讨论】:

  • 看看 REPLACE INTO .. 而不是 INSERT。但是,如果记录已存在,它将执行更新。
  • 看看你的表模式会很有趣。听起来您要做的是强制执行唯一索引,而字段本身没有唯一索引。虽然我可以看到您正在尝试使用相同的 uid 插入多行,但这对我来说真的没有意义。看起来您的问题可能可以通过表规范化和使用唯一索引来解决。
  • 我的想法是在提交之前和之后仔细检查 42。
  • 我和@Mike Brant 在一起,您似乎正在尝试使用 PHP 代码强制执行 unique 数据库列约束。
  • 为什么是 42?你意识到这不是真的生命、宇宙和一切的答案,对吧?

标签: php mysql pdo innodb


【解决方案1】:

您可以使用信号量类型的架构。例如,创建一个名为“信号量”的表或任何您想调用的表。假设表中只有一个字段,并且该字段是唯一的,称为“sem”。现在,运行“INSERT INTO semaphore SET sem = 42”;

好的,那个 INSERT 语句是原子的,这意味着在那之后当其他人试图插入 42 时,他们会得到一个错误,说明重复键。

然后,在原始表中进行插入。在事务中完成所有这些。在 SQL 中,它看起来像这样:

BEGIN TRANSACTION;
INSERT INTO semaphore SET sem = 42;
INSERT INTO t1 set uid=42, foo=1;
INSERT INTO t1 set uid=42, foo=2;
COMMIT;
DELETE FROM semaphore WHERE sem = 42;

您之后进行删除的原因有两个:

  1. 我猜你不需要删除它,但是让我们去寻找干净的数据吧 ;)
  2. 您在 COMMIT 之后删除的原因是您希望在事务完成之前保持阻塞锁。

旁注:信号量通常在自动增量字段不起作用时使用。然后,您使用其中只有一条记录的信号量表来序列化插入并阻止主键。

希望对你有帮助:)

【讨论】:

  • 感谢您的回答。我愿意这样做,尽管您会认为 SQL/InnoDB 有一个功能可以处理此问题,而无需为信号量创建新表。
  • 如果您需要将所有交易数据保存在一个表中,这绝对是一个可行的解决方案。
  • 您可以使用应用程序级锁来代替信号量表 - 请参阅GET_LOCK
猜你喜欢
  • 1970-01-01
  • 2013-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多