简体   繁体   English

插入子选择-原子操作?

[英]Insert with Subselect - Atomic Operation?

I know, that mysql supports auto-increment values, but no dependent auto-increment values. 我知道,mysql支持自动增量值,但没有相关的自动增量值。

ie if you have a table like this: 即,如果您有一个这样的表:

id | element | innerId
1  | a       | 1
2  | a       | 2
3  | b       | 1

And you insert another b -element, you need to compute the innerId on your own, (Excpected insert would be "2") 然后插入另一个b -element,您需要自己计算 innerId(预期插入为“ 2”)

  • Is there a database supporting something like this? 是否有支持这样的数据库?

What would be the best way to achieve this behaviour? 实现此行为的最佳方法是什么? I do not know the number of elements, so i cannot create dedicated tables for them, where I just could derrive an id. 我不知道元素的数量,因此我无法为它们创建专用表,而我只能在其中提供ID。

(The example is simplyfied) (这个例子很简单)

The target that should be achieved, is that any element "type" (where the number is unknown, possibly infitine -1 should have it's own, gap-less id. 应该实现的目标是任何元素“类型”(数量未知, possibly infitine -1都应具有自己的无间隙id)。

If I would use something like 如果我会使用类似

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 http://sqlfiddle.com/#!2/2f4543/1

Will this return the expected result under all circumstances? 在所有情况下,这都将返回预期的结果吗? I mean it works, but what about concurrency ? 我的意思是可以,但是并发呢? Are Inserts with SubSelects still atomic or might there be a szenario, where two inserts will try to insert the same id? 具有SubSelects的插入是否仍然是原子的,或者是否存在szenario,其中两个插入将尝试插入相同的ID? (Especially if a transactional insert is pending?) (特别是是否有待处理的事务插入?)

Would it be better to try to achieve this with the programming language (ie Java)? 尝试使用编程语言(例如Java)实现这一目标会更好吗? Or is it easier to implement this logic as close to the database engine as possible? 还是更容易在尽可能接近数据库引擎的地方实现这种逻辑?

Since I'm using an aggregation to compute the next innerId , i think using SELECT...FOR UPDATE can not avoid the problem in case of other transactions having pending commits, right? 由于我正在使用聚合来计算下一个innerId ,因此我认为使用SELECT...FOR UPDATE不能避免在其他事务具有未决提交的情况下出现问题,对吗?

ps.: I could ofc. 诗:我可以。 just bruteforce the insert - starting at the current max value per element - with a unique key constraint on (element,innerId) until there is no foreignKey-violation - but isn't there a nicer way? 只是强行插入-从每个元素的当前最大值开始-对(element,innerId)具有唯一的键约束(element,innerId)直到没有foreignKey-violation-但是没有更好的方法吗?

According to Make one ID with auto_increment depending on another ID - possible? 根据根据另一个ID通过auto_increment设置一个ID-可能吗? it would be possible with a composite primary key on - in my case - innerId and element . 在我的情况下,可以通过使用一个组合主键innerId and element But according to this setting MySQL auto_increment to be dependent on two other primary keys that works only for MyIsam (I have InnoDB) 但是根据此设置,MySQL auto_increment依赖于仅适用于MyIsam的其他两个主键 (我有InnoDB)


Now i'm confused even more. 现在我更加困惑了。 I tried to use 2 different php scripts to insert data, using the query above. 我尝试使用2种不同的php脚本通过上述查询插入数据。 While script one has a "sleep" for 15 seconds in order to allow me to call script two (which should simulate the concurrent modification) - The result was correct when using one query . 为了使我能够调用脚本2(应该模拟并发修改),脚本1处于“睡眠”状态15秒钟- 使用一个查询时结果正确。

(ps.: mysql(?!i) -functions only for quick debugging) (ps .: mysql(?!i) -仅用于快速调试的功能)

Base Data: 基本数据:

在此处输入图片说明

Script 1: 脚本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;");

Script 2: 脚本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;");

I would have expected that the page2 insert would have happened before the page1 insert, cause it's running without any transaction. 我本以为page2插入会在page1插入之前发生,因为它运行时没有任何事务。 But in fact, the page1 insert happened FIRST, causing the second script to also be delayed for about 15 seconds... 但是实际上,page1插入是最先发生的,导致第二个脚本也被延迟了约15秒...

(ignore the AC-Id, played around a bit) (忽略AC-Id,大约播放了一下)

在此处输入图片说明

When using Rollback on the first script, the second script is still delayed for 15 seconds, and then picking up the correct innerId : 在第一个脚本上使用Rollback时,第二个脚本仍然延迟15秒, 然后选择正确的innerId

在此处输入图片说明

So : 因此

  • Non-Transactional-Insert are blocked while a transaction is active. 事务处于活动状态时,Non-Transactional-Insert被阻止。
  • Inserts with subselects seem also to be blocked. 具有子选择的插入似乎也被阻止。
  • So at the end it seems like a Insert with a subselect is an atomic operation? 因此,最后似乎带有子选择的Insert是原子操作? Or why would the SELECT of the second page has been blocked otherwhise? 还是为什么第二页的SELECT会被阻止呢?

Using the selection and insert in seperate, non-transactional statements like this (on page 2, simulating the concurrent modification): 使用选择并在这样的单独的非事务性语句中插入(在第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')");

leads to the error I was trying to avoid: 导致我试图避免的错误:

在此处输入图片说明

so why does It work in the concurrent szenario when each modification is one query? 那么,当每个修改都是一个查询时,为什么它在并发szenario中起作用? Are inserts with subselects atomic? 带有子选择的插入是原子的吗?

Well, all (or almost) all databases support the necessary functionality for calculating innerid according to your rules. 好吧,所有(或几乎所有)数据库都支持根据您的规则计算innerid的必要功能。 It is called a trigger, specifically a before insert trigger. 这称为触发器,特别是插入前触发器。

Your particular version will not work consistently in a multi-user environment. 您的特定版本在多用户环境中将无法正常运行。 Few, if any, databases generate read locks on a table when starting an insert. 开始插入时,很少有数据库会在表上生成读取锁。 That means that two insert statements issued very close together would generate the same value for innerid . 这意味着两个非常靠近发出的insert语句将为innerid生成相同的值。

Because of concurrency considerations, you should do this calculation in the database, using triggers rather than on the application side. 考虑到并发性,您应该在数据库中使用触发器而不是在应用程序端进行此计算。

You always have the possibility of calculating innerid when you need it, rather than when you insert the value. 您始终可以在需要时计算innerid ,而不是在插入值时进行计算。 This is computationally expensive, requiring either an order by (using variables) or a correlated subquery. 这在计算上很昂贵,需要order by (使用变量)进行order by或使用相关子查询。 Other databases support window/analytic functions, making such a calculation much easier to express. 其他数据库支持窗口/分析功能,使这种计算更容易表达。

From what I read here: Atomicity multiple MySQL subqueries in an INSERT/UPDATE query? 从我在这里看到的内容: 原子性INSERT / UPDATE查询中的多个MySQL子查询? your query seems to be atomic. 您的查询似乎是原子的。 I've tested it on my MySQL with InnoDB with 4 different programs trying to execute the query 100000 times each. 我已经在我的带有InnoDB的MySQL上对它进行了测试,并使用4种不同的程序尝试分别执行该查询100000次。 After that I was able to create a combined Unique key on (element,innerid) and it worked well, so it didn't seem to generate a duplicate. 之后,我能够在(element,innerid)上创建一个组合的唯一键,并且效果很好,因此似乎没有生成重复键。 However I've got: 但是我有:

Deadlock found when trying to get lock 尝试获取锁时发现死锁

So you might want to consider this http://dev.mysql.com/doc/refman/5.1/en/innodb-deadlocks.html 因此,您可能需要考虑使用此http://dev.mysql.com/doc/refman/5.1/en/innodb-deadlocks.html

EDIT: It seems I could circumvent the deadlock by changing the SQL to 编辑:似乎我可以通过将SQL更改为来避免死锁

INSERT INTO test (id,element,innerId) VALUES(null, "b", (SELECT Count(*) FROM test t2 WHERE element = 'b' FOR UPDATE )+1); 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