繁体   English   中英

插入子选择-原子操作?

[英]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

在此处输入图片说明

因此

  • 事务处于活动状态时,Non-Transactional-Insert被阻止。
  • 具有子选择的插入似乎也被阻止。
  • 因此,最后似乎带有子选择的Insert是原子操作? 还是为什么第二页的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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM