我知道,mysql 支持自动增量值,但没有依赖自动增量值。
即如果你有一个像这样的表:
id | element | innerId
1 | a | 1
2 | a | 2
3 | b | 1
并且您插入另一个b
元素,您需要自己计算innerId,(预期插入将为“2”)
- 有数据库支持这样的事情吗?
实现这种行为的最佳方法是什么?我不知道元素的数量,所以我无法为它们创建专用表,我只能在其中导出 id。
(例子很简单)
应该实现的目标是任何元素“类型”(其中数字未知,可能是 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
在所有情况下都会返回预期结果吗?我的意思是它可以工作,但是并发呢?带有 SubSelect 的插入仍然是原子的还是可能存在一个场景,其中两个插入将尝试插入相同的 id? (特别是如果事务插入处于待处理状态?)
尝试使用编程语言(即 Java)来实现这一目标会更好吗?还是尽可能靠近数据库引擎来实现此逻辑更容易?
由于我使用聚合来计算下一个innerId,我认为使用SELECT...FOR UPDATE
无法避免其他事务发生时出现的问题待处理的提交,对吗?
ps.:我可以。只是暴力插入 - 从每个元素的当前最大值开始 - 对 (element,innerId)
施加唯一键约束,直到没有外键违规 - 但不是'有没有更好的方法?
根据Make one ID with auto_increment depending on another ID - possible?在我的例子中,可以使用 innerId 和 element
上的复合主键。但根据这个setting MySQL auto_increment to be dependent on two other primary keys仅适用于 MyIsam(我有 InnoDB)
现在我更困惑了。我尝试使用 2 个不同的 php 脚本来插入数据,使用上面的查询。虽然脚本一有 15 秒的“ sleep ”时间,以便允许我调用脚本二(这应该模拟并发修改) - 使用一个查询时结果是正确的。
(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插入首先发生,导致第二个脚本也延迟了大约15秒......
(忽略 AC-Id,稍微玩了一下)
在第一个脚本上使用回滚
时,第二个脚本仍然延迟 15 秒,然后然后选取正确的内部Id
:
所以:
- 非事务性插入在事务处于 Activity 状态时被阻止。
- 带有子选择的插入似乎也被阻止。
- 所以最后看起来带有子选择的插入是一个原子操作?或者为什么第二页的
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')");
导致我试图避免的错误:
那么当每次修改都是一个查询时,为什么它可以在并发场景中工作呢? 带有子选择的插入是原子的吗?
最佳答案
好吧,所有(或几乎)所有数据库都支持根据您的规则计算 innerid
的必要功能。它称为触发器,特别是插入前触发器。
您的特定版本在多用户环境中无法一致工作。很少(如果有的话)数据库在开始插入时在表上生成读锁。这意味着两个非常接近地发出的插入语句将为 innerid
生成相同的值。
出于并发性考虑,您应该使用触发器在数据库中执行此计算,而不是在应用程序端。
您始终可以在需要时计算 innerid
,而不是在插入值时计算。这在计算上是昂贵的,需要order by
(使用变量)或相关子查询。其他数据库支持窗口/分析函数,使得这样的计算更容易表达。
关于java - 使用子选择插入 - 原子操作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26722153/