当前位置: 首页 > 知识库问答 >
问题:

手动增量的复合键

温浩大
2023-03-14

如何在多会话/事务环境中安全地将一行插入到包含带有(手动)增量键的主复合键的表中。

如何获取<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个答案

    皇甫高阳
    2023-03-14

    让我们将包含这3列的表称为ThreeClonTable,以避免因您给出的名称-table而引起混淆。

    手动递增的是列column_c。将其拉出来并在另一个表中跟踪用于该列的最后一个值。

    解决方案的步骤:

    • 创建一个表,存储column_c使用的最后一个值。让我们将此表称为LastUsedIdTableLastUsedIdTable将仅包含三列:
      • 要手动递增其列的表的名称(示例: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 将所有此类主键列的最后一个值存储在数据库的所有表中。由于此表包含的行数与所有数据库中要手动递增的键数一样多,因此它仅包含有限且固定的行数。这将是快速且无争用条件的。

    唐景山
    2023-03-14
    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的设置很重要,但这值得研究。

    叶谦
    2023-03-14

    您可以使用存储过程来实现此目的:

    我从来没有遇到过这样的问题,如果我遇到过,我会这样做:

    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): 有关的: 如