java - 使用子选择插入 - 原子操作?

标签 java mysql transactions atomic

我知道,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)-仅用于快速调试的函数)

基础数据:

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插入首先发生,导致第二个脚本也延迟了大约15秒......

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

enter image description here

在第一个脚本上使用回滚时,第二个脚本仍然延迟 15 秒,然后然后选取正确的内部Id:

enter image description here

所以:

  • 非事务性插入在事务处于 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')");

导致我试图避免的错误:

enter image description here

那么当每次修改都是一个查询时,为什么它可以在并发场景中工作呢? 带有子选择的插入是原子的吗?

最佳答案

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

您的特定版本在多用户环境中无法一致工作。很少(如果有的话)数据库在开始插入时在表上生成锁。这意味着两个非常接近地发出的插入语句将为 innerid 生成相同的值。

出于并发性考虑,您应该使用触发器在数据库中执行此计算,而不是在应用程序端。

您始终可以在需要时计算 innerid,而不是在插入值时计算。这在计算上是昂贵的,需要order by(使用变量)或相关子查询。其他数据库支持窗口/分析函数,使得这样的计算更容易表达。

关于java - 使用子选择插入 - 原子操作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26722153/

相关文章:

java - System.out.println() 是一个字段的方法吗?

java - 竞争性编程和输入

php - 从 MySQL 到 PDO - 存储用户指定的日期字段

mysql - DATETIME 选择过去 2 小时内的行

java - 从非事务方法调用的多个事务方法的传播级别

java - 从 Android Studio 中的 CSV 文件中获取信息?

java - 如何修复错误 "Statemachine is not in state ready to do DELETE"

sql - 以重复 key 更新为条件

java - 当一个数据源事务失败时,Spring 无法回滚已提交的事务

mysql - 在innoDB中使用触发器安全吗?