如何在多会话/事务环境中安全地将一行插入到包含带有(手动)增量键的主复合键的表中。
如何获取<code>column_c</code>的最新增量值,<code>LAST_INSERT_ID()</code>不返回所需值。
我已经研究了< code >选择更新...INSERT和< code>INSERT INTO SELECT,但无法决定使用哪一个。
从事务安全(锁)、隔离级别和性能角度来看,实现这一点的最佳方法是什么。
更新-对问题的另一种看法
假设两个事务/会话尝试插入相同的column_a,column_b对(示例 1,1)。我该怎么做;
>
按顺序执行插入查询。第一次插入(事务1)应产生1,1,1的复合密钥,第二次插入(交易2)应产生2,1,2的复合密钥
检索插入column_c值。我可能需要利用变量?
表格定义
CREATE TABLE `table` (
`column_a` int(11) unsigned NOT NULL,
`column_b` int(11) unsigned NOT NULL,
`column_c` int(11) unsigned NOT NULL,
PRIMARY KEY (column_a, column_b, column_c)
) ENGINE=InnoDB;
Exempel data
+----------+----------+----------+
| column_a | column_b | column_c |
+----------+----------+----------+
| 1 | 1 | 1 |
| 1 | 1 | 2 |
| 1 | 1 | 3 |
| 2 | 1 | 1 |
| 2 | 1 | 2 |
| 2 | 1 | 3 |
+----------+----------+----------+
接受插入到select查询
INSERT INTO `table` (`column_a`, `column_b`, `column_c`)
SELECT 2,1, IFNULL(MAX(`column_c`), 0) + 1 FROM `table`
WHERE `column_a` = 2 and `column_b` = 1;
让我们将包含这3列的表称为ThreeClonTable
,以避免因您给出的名称-table
而引起混淆。
手动递增的是列column_c
。将其拉出来并在另一个表中跟踪用于该列的最后一个值。
解决方案的步骤:
column_c
使用的最后一个值。让我们将此表称为LastUsedIdTable
。LastUsedIdTable
将仅包含三列:
ThreeClonTable
);column_c
);121
)。证明解决方案的可行性:
为了使演示更一般化,假设在< code>ThreeColumnTable中有n个并发的插入请求。
所有n个请求都必须首先调用存储过程。由于Stored Proc在LastUsedIdTable
上使用事务,因此一次只有1个请求将访问ThreeClonTable
的行,目前如下所示:
+-----------------------------------+
| ThreeColumnTable | column_c | 121 |
+-----------------------------------+
现在,第一个请求将锁定这一行并获取< code>122作为值,还将表的行中的值更新为122。当n个请求完成时,< code>LastUsedIdTable的< code>ThreeColumnTable行将如下所示:
+-----------------------------------------+
| ThreeColumnTable | column_c | (121 + n) |
+-----------------------------------------+
现在,这些n个请求已经在ThreeColumnTable
中执行插入的路上了。但是,由于它们都有自己独特的column_c
值,因此无论插入的顺序如何,它们的插入都将没有冲突!您可以先插入值 121 n
,最后插入值 122
。事实上,您甚至不需要column_a、column_b column_c
元组是唯一的,因为column_c
将始终是唯一的。
此解决方案适用于并行请求和串行请求。由于对性能的影响非常非常小(可以忽略不计),这将是您尝试达到的交易安全,隔离级别和性能的最佳点。
要点: 使用 LastUsedId
将所有此类主键列的最后一个值存储在数据库的所有表中。由于此表包含的行数与所有数据库中要手动递增的键数一样多,因此它仅包含有限且固定的行数。这将是快速且无争用条件的。
BEGIN;
SELECT @c := MAX(c) + 1
FROM t
WHERE a = ? AND b = ?
FOR UPDATE; -- important
if row found -- in application code (or Stored Proc)
then
INSERT INTO t (a,b,c)
VALUES
(?, ?, @c);
else
INSERT INTO t (a,b,c)
VALUES
(?, ?, 1);
COMMIT;
希望< code>FOR UPDATE会停止,直到它可以获得锁和所需的< code>c值。那么接下来的交易应该会很顺利。
我不认为transaction_isolation
的设置很重要,但这值得研究。
您可以使用存储过程来实现此目的:
我从来没有遇到过这样的问题,如果我遇到过,我会这样做:
CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_insert_when_duplicate`(val1 int, val2 int, val3 int)
BEGIN
-- catch duplicate insert error
DECLARE EXIT HANDLER FOR 1062
BEGIN
-- we could recursively try to insert the same val1 and val2 but increasing val3 by 1
call sp_insert_when_duplicate(val1,val2,val3+1);
END;
-- by default mysql recursive limit is 0, you could set as 10 or 100 as per your wish
SET max_sp_recursion_depth=10;
-- [Trying] to insert the values, if no duplicate this should continue and end the script.. if duplicate, above handler should catch and try to insert again with 1+ value for val3
INSERT INTO `table` (`column_a`, `column_b`, `column_c`) values (val1,val2,val3);
END
用法如下:
call sp_insert_when_duplicate(1,1,1);
call sp_insert_when_duplicate(1,1,1);
call sp_insert_when_duplicate(1,1,1);
call sp_insert_when_duplicate(2,1,1);
call sp_insert_when_duplicate(2,1,1);
call sp_insert_when_duplicate(2,2,1);
select * from `table`;
结果:
+----------+----------+----------+
| column_a | column_b | column_c |
+----------+----------+----------+
| 1 | 1 | 1 |
| 1 | 1 | 2 |
| 1 | 1 | 3 |
| 2 | 1 | 1 |
| 2 | 1 | 2 |
| 2 | 2 | 1 |
+----------+----------+----------+
同样适用于交易:
start transaction;
call sp_insert_when_duplicate(1,1,1);
call sp_insert_when_duplicate(1,1,1);
call sp_insert_when_duplicate(1,1,1);
call sp_insert_when_duplicate(2,1,1);
call sp_insert_when_duplicate(2,1,1);
call sp_insert_when_duplicate(2,2,1);
commit;
select * from `table`;
+----------+----------+----------+
| column_a | column_b | column_c |
+----------+----------+----------+
| 1 | 1 | 1 |
| 1 | 1 | 2 |
| 1 | 1 | 3 |
| 2 | 1 | 1 |
| 2 | 1 | 2 |
| 2 | 2 | 1 |
+----------+----------+----------+
然而,我还没有尝试过并行事务!
问题内容: 我想为InnoDB MySQL表创建类似于MyISAM的行为。我想要一个复合主键: 主键(id1,id2) 其中id1根据id2的值自动递增。用InnoDB做到这一点的最佳方法是什么? 问题答案: 您可以使用此BEFORE INSERT触发器来替换零个id值- 然后插入零值(id1或id2)以生成新值-
问题内容: 我有这样的代码 即使我非常确定我是第一次写记录,此代码也会给我一个 sqlite3.IntegrityError 异常。 所以,我尝试 这会将行细插入 基 表BUT中,ID列根本不会自动递增。 我能做什么?任何想法? 问题答案: 在sqlite中,仅当一个整数列是主键时,您才获得自动增量行为。复合键阻止自动增量生效。 通过定义为唯一的主键,然后在上添加一个附加的唯一约束,您可以获得类似
问题内容: 我希望自己的复合关键元素之一自动递增,并且我将Embedded Key用于实体类,因此由于使用或时注释会被忽略,因此我无法使用“生成的值注释”来解决问题。 问题答案: 您不能使用与,或,在注释被忽略。 您必须手动分配值。 检查以下参考: https://java.net/jira/browse/GLASSFISH-13567 https://forums.hibernate.org/v
《复制增量》又是一款受到《反物质维度》启发的放置游戏。
问题内容: 是否可以在MySQL中制作结合了alpha元素和数字的自动增量功能? 我有一个具有QAb99,QAb101,QAd9003等键的现有系统。最大数字部分的范围为1到9999,而字母以QA开头,范围从QAa到QAd等,最终将通过QAd9999到QAe1等。 。 在SQL或SQL外部(例如php脚本)管理生成新密钥是否更好? 谢谢。 问题答案: 我前一阵子问过。不幸的是,Mysql并没有这样
问题内容: 使用Postgres,我试图用SQL自动编号主键。但是,这给了我一个错误。 错误: 知道为什么吗? 问题答案: Postgres 10或更高版本 列(请参见下文)保持不变。但是考虑一个 专栏。Postgres10实现了此标准SQL功能。 手册中的基本语法和信息。 __用列 创建 表 将 列 添加 到现有表 表可能填充也可能不填充行。 同时使它成为PK(表还不能拥有PK): 有关的: 如