[英]Insert with Subselect - Atomic Operation?
我知道,mysql支持自动增量值,但没有相关的自动增量值。
即,如果您有一个这样的表:
id | element | innerId
1 | a | 1
2 | a | 2
3 | b | 1
然后插入另一个b
-element,您需要自己计算 innerId(预期插入为“ 2”)
实现此行为的最佳方法是什么? 我不知道元素的数量,因此我无法为它们创建专用表,而我只能在其中提供ID。
(这个例子很简单)
应该实现的目标是任何元素“类型”(数量未知, 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的插入是否仍然是原子的,或者是否存在szenario,其中两个插入将尝试插入相同的ID? (特别是是否有待处理的事务插入?)
尝试使用编程语言(例如Java)实现这一目标会更好吗? 还是更容易在尽可能接近数据库引擎的地方实现这种逻辑?
由于我正在使用聚合来计算下一个innerId ,因此我认为使用SELECT...FOR UPDATE
不能避免在其他事务具有未决提交的情况下出现问题,对吗?
诗:我可以。 只是强行插入-从每个元素的当前最大值开始-对(element,innerId)
具有唯一的键约束(element,innerId)
直到没有foreignKey-violation-但是没有更好的方法吗?
根据根据另一个ID通过auto_increment设置一个ID-可能吗? 在我的情况下,可以通过使用一个组合主键innerId and element
。 但是根据此设置,MySQL auto_increment依赖于仅适用于MyIsam的其他两个主键 (我有InnoDB)
现在我更加困惑了。 我尝试使用2种不同的php脚本通过上述查询插入数据。 为了使我能够调用脚本2(应该模拟并发修改),脚本1处于“睡眠”状态15秒钟- 使用一个查询时结果正确。
(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,大约播放了一下)
在第一个脚本上使用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中起作用? 带有子选择的插入是原子的吗?
好吧,所有(或几乎所有)数据库都支持根据您的规则计算innerid
的必要功能。 这称为触发器,特别是插入前触发器。
您的特定版本在多用户环境中将无法正常运行。 开始插入时,很少有数据库会在表上生成读取锁。 这意味着两个非常靠近发出的insert语句将为innerid
生成相同的值。
考虑到并发性,您应该在数据库中使用触发器而不是在应用程序端进行此计算。
您始终可以在需要时计算innerid
,而不是在插入值时进行计算。 这在计算上很昂贵,需要order by
(使用变量)进行order by
或使用相关子查询。 其他数据库支持窗口/分析功能,使这种计算更容易表达。
从我在这里看到的内容: 原子性INSERT / UPDATE查询中的多个MySQL子查询? 您的查询似乎是原子的。 我已经在我的带有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);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.