我知道,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)
- 仅用于快速调试的功能)
基础数据:
脚本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,玩了一下)
在第一个脚本上使用Rollback
时,第二个脚本仍延迟15秒,然后选择正确的innerId
:
因此:
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')");
导致我试图避免的错误:
那么为什么在每个修改是一个查询时,它在并发szenario中有效? 具有子选择的插入是原子的吗?
答案 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);