【问题标题】:mysql two column primary key with auto-incrementmysql 两列自增主键
【发布时间】:2011-07-21 22:53:23
【问题描述】:

我有多个具有相同结构的数据库,有时会在其中复制数据。为了保持数据完整性,我使用两列作为主键。一个是数据库 ID,它链接到包含每个数据库信息的表。另一个是表键。它不是唯一的,因为它可能有多个行,此值相同,但 database_id 列中的值不同。

我打算将这两列变成一个联合主键。但是,我还想将表键设置为自动递增 - 但基于 database_id 列。

EG,有了这些数据:

table_id   database_id     other_columns
1          1
2          1
3          1
1          2
2          2

如果我添加的数据包含 1 的 dabase_id,那么我希望 table_id 自动设置为 4。如果 dabase_id 输入为 2,那么我希望 table_id 自动设置为 3。等等。

在 MySql 中实现这一目标的最佳方法是什么。

【问题讨论】:

    标签: mysql


    【解决方案1】:

    如果您使用的是 myisam

    http://dev.mysql.com/doc/refman/5.0/en/example-auto-increment.html

    对于 MyISAM 和 BDB 表,您可以 在辅助节点上指定 AUTO_INCREMENT 多列索引中的列。在 在这种情况下,生成的值 AUTO_INCREMENT 列计算为 MAX(auto_increment_column) + 1 哪里 前缀=给定前缀。这很有用 当您想将数据排序时 组。

    CREATE TABLE animals (
        grp ENUM('fish','mammal','bird') NOT NULL,
        id MEDIUMINT NOT NULL AUTO_INCREMENT,
        name CHAR(30) NOT NULL,
        PRIMARY KEY (grp,id)
    ) ENGINE=MyISAM;
    
    INSERT INTO animals (grp,name) VALUES
        ('mammal','dog'),('mammal','cat'),
        ('bird','penguin'),('fish','lax'),('mammal','whale'),
        ('bird','ostrich');
    
    SELECT * FROM animals ORDER BY grp,id;
    
    Which returns:
    
    +--------+----+---------+
    | grp    | id | name    |
    +--------+----+---------+
    | fish   |  1 | lax     |
    | mammal |  1 | dog     |
    | mammal |  2 | cat     |
    | mammal |  3 | whale   |
    | bird   |  1 | penguin |
    | bird   |  2 | ostrich |
    +--------+----+---------+
    

    你的例子:

    mysql> CREATE TABLE mytable (
        ->     table_id MEDIUMINT NOT NULL AUTO_INCREMENT,
        ->     database_id MEDIUMINT NOT NULL,
        ->     other_column CHAR(30) NOT NULL,
        ->     PRIMARY KEY (database_id,table_id)
        -> ) ENGINE=MyISAM;
    Query OK, 0 rows affected (0.03 sec)
    
    mysql> INSERT INTO mytable (database_id, other_column) VALUES
        ->     (1,'Foo'),(1,'Bar'),(2,'Baz'),(1,'Bam'),(2,'Zam'),(3,'Zoo');
    Query OK, 6 rows affected (0.00 sec)
    Records: 6  Duplicates: 0  Warnings: 0
    
    mysql> SELECT * FROM mytable ORDER BY database_id,table_id;
    +----------+-------------+--------------+
    | table_id | database_id | other_column |
    +----------+-------------+--------------+
    |        1 |           1 | Foo          |
    |        2 |           1 | Bar          |
    |        3 |           1 | Bam          |
    |        1 |           2 | Baz          |
    |        2 |           2 | Zam          |
    |        1 |           3 | Zoo          |
    +----------+-------------+--------------+
    6 rows in set (0.00 sec)
    

    【讨论】:

    • 警告!这会导致复制问题,请参阅dev.mysql.com/doc/refman/5.1/en/…An INSERT into a table that has a composite primary key that includes an AUTO_INCREMENT column that is not the first column of this composite key is not safe [..]
    • @Demonslay335 这似乎不是真的——这在带有 InnoDB 的 MySQL 5.5 中不起作用。因此,没有带有事务的辅助 AUTO_INCREMENT...
    • @Brilliand Odd,我在文档中看到了。我确实遇到了它无法在我的生产服务器(MySQL 5.5.34)上运行的问题。我最终让我的应用程序使用与数据库在内部完成的相同查询来完成工作 (MAX(auto_increment_column) + 1 WHERE prefix=given-prefix)。
    • @Demonslay335 它在 InnoDB 中不起作用,除非您将主键的顺序更改为 (id,grp) 而不是 (grp,id),显然自动增量列必须先行。跨度>
    • 据我所知,该功能永远不会被放入 InnoDB。我在 8.0 中看不到它。建议你在 bugs.mysql.com 上投票。
    【解决方案2】:

    这是使用 innodb 时的一种方法,由于聚集复合索引,它也将非常高效 - 仅适用于 innodb...

    http://dev.mysql.com/doc/refman/5.0/en/innodb-index-types.html

    drop table if exists db;
    create table db
    (
    db_id smallint unsigned not null auto_increment primary key,
    next_table_id int unsigned not null default 0
    )engine=innodb;
    
    drop table if exists tables;
    create table tables
    (
    db_id smallint unsigned not null,
    table_id int unsigned not null default 0,
    primary key (db_id, table_id) -- composite clustered index
    )engine=innodb;
    
    delimiter #
    
    create trigger tables_before_ins_trig before insert on tables
    for each row
    begin
    declare v_id int unsigned default 0;
    
      select next_table_id + 1 into v_id from db where db_id = new.db_id;
      set new.table_id = v_id;
      update db set next_table_id = v_id where db_id = new.db_id;
    end#
    
    delimiter ;
    
    
    insert into db (next_table_id) values (null),(null),(null);
    
    insert into tables (db_id) values (1),(1),(2),(1),(3),(2);
    
    select * from db;
    select * from tables;
    

    【讨论】:

    • 抱歉,我应该指定我使用的是 MyISAM。无论如何都支持你。
    • @f00,这种方法需要应用级同步,以防多个线程同时更新同一个数据库,对吧?
    【解决方案3】:

    您可以将两列主键设为unique自动递增键设为primary

    【讨论】:

      【解决方案4】:

      DTing 提供的解决方案非常出色且有效。但是当在 AWS Aurora 中尝试相同的方法时,它没有工作并抱怨以下错误。

      Error Code: 1075. Incorrect table definition; there can be only one auto column and it must be defined as a key
      

      因此在这里建议基于 json 的解决方案。

      CREATE TABLE DB_TABLE_XREF (
          db             VARCHAR(36) NOT NULL,
          tables         JSON,
          PRIMARY KEY    (db)
      )
      

      第一个主键在外面,第二个主键在json里面,第二个主键值为auto_incr_sequence。

      INSERT INTO `DB_TABLE_XREF`
        (`db`,  `tables`)
      VALUES
        ('account_db', '{"user_info": 1, "seq" : 1}')
      ON DUPLICATE KEY UPDATE `tables` =
        JSON_SET(`tables`,
                 '$."user_info"',
                 IFNULL(`tables` -> '$."user_info"', `tables` -> '$."seq"' + 1),
                 '$."seq"',
                 IFNULL(`tables` -> '$."user_info"', `tables` -> '$."seq"' + 1)
        );
      

      输出如下所示

      account_db    {"user_info" : 1, "user_details" : 2, "seq" : 2}
      product_db    {"product1" : 1, "product2" : 2,  "product3" : 3, "seq" : 3}
      

      如果您的辅助键很大,并且害怕使用 json,那么我建议您使用存储过程,检查 MAX(secondary_column) 以及如下所示的锁。

      SELECT table_id INTO t_id FROM DB_TABLE_XREF WHERE database = db_name AND table = table_name;
      IF t_id = 0 THEN
           SELECT GET_LOCK(db_name, 10) INTO acq_lock;
           -- CALL debug_msg(TRUE, "Acquiring lock");
           IF acq_lock = 1 THEN
               SELECT table_id INTO t_id FROM DB_TABLE_XREF WHERE database_id = db_name AND table = table_name;
               -- double check lock
               IF t_id = 0 THEN
                    SELECT IFNULL((SELECT MAX(table_id) FROM (SELECT table_id FROM DB_TABLE_XREF WHERE database = db_name) AS something), 0) + 1 into t_id;
                    INSERT INTO DB_TABLE_XREF VALUES (db_name, table_name, t_id);
               END IF;
           ELSE 
           -- CALL debug_msg(TRUE, "Failed to acquire lock");
      END IF;
      COMMIT;
      

      【讨论】:

      • 我不明白这与问题有什么关系。
      • @EatonEmmerich 用户想要的只是基于两列的序列开始。在 Innodb 引擎中是不可能的。因此建议使用 json 方式来做到这一点。
      • 那么这些 sql 查询会在 InnoDB 上运行吗?
      • @EatonEmmerich 是的。
      猜你喜欢
      • 1970-01-01
      • 2015-06-30
      • 1970-01-01
      • 2011-04-26
      • 2010-12-09
      • 2020-11-04
      • 2012-01-31
      • 1970-01-01
      • 2018-06-05
      相关资源
      最近更新 更多