插入Subselect - 原子操作?

时间:2014-11-03 20:01:32

标签: java mysql transactions atomic

我知道,mysql支持自动增量值,但没有依赖自动增量值。

即。如果你有这样的表:

id | element | innerId
1  | a       | 1
2  | a       | 2
3  | b       | 1

你插入另一个b - 元素,你需要自己计算 innerId,(感兴趣的插入将是" 2")

  • 是否有支持此类内容的数据库?

实现这种行为的最佳方法是什么?我不知道元素的数量,所以我不能为它们创建专用的表,我可以在这里创建一个id。

(简单的例子)

应该实现的目标是任何元素" type" (如果数字未知,possibly infitine -1应该拥有自己的无差距ID。

如果我会使用像

这样的东西
INSERT INTO 
  myTable t1 
   (id,element, innerId)
   VALUES
   (null, 'b', (SELECT COUNT(*) FROM myTable t2 WHERE t2.element = "b") +1)

http://sqlfiddle.com/#!2/2f4543/1

这会在所有情况下都返回预期结果吗?我的意思是它有效,但并发呢?具有SubSelects的插入是 atomic 还是可能存在szenario,其中两个插入将尝试插入相同的ID? (特别是如果事务插入处于待处理状态?)

使用编程语言(即Java)尝试实现这一点更好吗?或者更容易将该逻辑实现为尽可能靠近数据库引擎?

由于我使用聚合来计算 next innerId ,我认为使用SELECT...FOR UPDATE无法避免在其他事务具有待处理提交的情况下出现问题,对吗? / p> ps:我可以。只是 bruteforce 插入 - 从每个元素的当前最大值开始 - 在(element,innerId)上使用唯一键约束,直到没有foreignKey违规 - 但是没有更好方式?

根据Make one ID with auto_increment depending on another ID - possible?,可以使用复合主键 - 在我的情况下 - innerId and element。但根据这个setting MySQL auto_increment to be dependent on two other primary keys仅适用于MyIsam(我有InnoDB)


现在我更加困惑了。我尝试使用2个不同的PHP脚本来插入数据,使用上面的查询。脚本1有一个" sleep" 15秒,以便允许我调用脚本2(应该模拟并发修改) - 使用一个查询时结果是正确的

(ps。:mysql(?!i) - 仅用于快速调试的功能)

基础数据:

enter image description here

脚本1:

mysql_query("START TRANSACTION");
mysql_query("INSERT INTO insertTest (id, element, innerId, fromPage)VALUES(null, 'a', (SELECT MAX(t2.innerID) FROM insertTest t2 WHERE element='a') +1, 'page1')");

sleep(15);

//mysql_query("ROLLBACK;");
mysql_query("COMMIT;");

脚本2:

//mysql_query("START TRANSACTION");
mysql_query("INSERT INTO insertTest (id, element, innerId, fromPage)VALUES(null, 'a', (SELECT MAX(t2.innerID) FROM insertTest t2 WHERE element='a') +1, 'page2')");
//mysql_query("COMMIT;");

我原本期望在page2插入之前发生page1插入,导致它在没有任何事务的情况下运行。但实际上,page1插入发生了FIRST,导致第二个脚本也被延迟了大约15秒......

(忽略AC-Id,玩了一下)

enter image description here

在第一个脚本上使用Rollback时,第二个脚本延迟15秒,然后选择正确的innerId

enter image description here

因此

  • 在事务处于活动状态时阻止非事务性插入。
  • 带有子选择的插入内容似乎也会被阻止。
  • 所以最后它似乎是一个带有子选择的插入是一个原子操作?或者为什么第二页的SELECT被阻止了?

使用选择并插入单独的非事务性语句(第2页,模拟并发修改):

$nextId = mysql_query("SELECT MAX(t2.innerID) as a FROM insertTest t2 WHERE element='a'");
$nextId = mysql_fetch_array($nextId);
$nextId = $nextId["a"] +1;
mysql_query("INSERT INTO insertTest (id, element, innerId, fromPage)VALUES(null, 'a', $nextId, 'page2')");

导致我试图避免的错误:

enter image description here

那么为什么在每个修改是一个查询时,它在并发szenario中有效? 具有子选择的插入是原子的吗?

2 个答案:

答案 0 :(得分:1)

嗯,所有(或几乎)所有数据库都支持根据您的规则计算innerid所需的功能。它被称为触发器,特别是在插入触发器之前。

您的特定版本在多用户环境中无法始终如一地运行。启动插入时,很少(如果有)数据库在表上生成 read 锁。这意味着两个非常接近的插入语句将为innerid生成相同的值。

由于并发方面的考虑,您应该使用触发器而不是在应用程序端在数据库中进行此计算。

您总是可以在需要时计算innerid,而不是在插入值时计算order by。这在计算上很昂贵,需要{{1}}(使用变量)或相关子查询。其他数据库支持窗口/分析功能,使这种计算更容易表达。

答案 1 :(得分:1)

从我在这里读到:Atomicity multiple MySQL subqueries in an INSERT/UPDATE query?你的查询似乎是原子的。我已经使用InnoDB在我的MySQL上测试了它,其中有4个不同的程序尝试每次执行100000次查询。之后我能够在(element,innerid)上创建组合的唯一键,并且它运行良好,所以它似乎没有生成重复。但是我得到了:

尝试锁定时发现死锁

所以你可能想要考虑这个http://dev.mysql.com/doc/refman/5.1/en/innodb-deadlocks.html

编辑:我似乎可以通过将SQL更改为

来规避死锁

INSERT INTO test(id,element,innerId)           VALUES(null," b",                  (SELECT Count(*)FROM test t2 WHERE element =' b' FOR UPDATE )+1);