MySQL双列主键自动增量

31

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

我计划将这两列合并为联合主键。但我还想将表键设置为自动递增 - 但基于database_id列。

例如,对于以下数据:

table_id   database_id     other_columns
1          1
2          1
3          1
1          2
2          2
如果我正在添加包含database_id为1的数据,则希望将table_id自动设置为4。如果输入的database_id为2,则希望将table_id自动设置为3,以此类推。
在MySql中实现这个功能的最佳方法是什么?
4个回答

44

如果你正在使用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 WHERE prefix = given-prefix。当你想要将数据放入有序分组时,这是非常有用的。

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)

4
警告!这会导致复制问题,请参见http://dev.mysql.com/doc/refman/5.1/en/replication-features-auto-increment.html。如果向具有组合主键的表中插入数据,并且此组合键包括一个不是该组合键的第一列的AUTO_INCREMENT列,则这不是安全的操作。 - Tino
4
@Demonslay335,这似乎不是真的 - 在MySQL 5.5中使用InnoDB不起作用。因此,在事务中没有次要的AUTO_INCREMENT... - Brilliand
2
@Brilliand,我在文档中看到了这个。但是在我的生产服务器上(MySQL 5.5.34),我遇到了它无法工作的问题。最终,我让我的应用程序通过使用数据库内部执行的相同查询(MAX(auto_increment_column) + 1 WHERE prefix=given-prefix)来完成这项工作。 - Demonslay335
4
除非你将主键的顺序更改为(id,grp)而不是(grp,id),否则在InnoDB中无法工作。显然,自增列必须放在第一位。 - gansodesoya
2
据我所知,该功能将永远不会被放入InnoDB中。我在8.0版本中也没有看到它。建议您在bugs.mysql.com上为其投票。 - Rick James
显示剩余2条评论

26

这是使用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。无论如何,我还是给你点赞了。 - SystemicPlural
1
请阅读以下内容:http://www.mysqlperformanceblog.com/2011/03/18/video-the-innodb-storage-engine-for-mysql/,该内容与MySQL的InnoDB存储引擎有关。 - Jon Black
@f00,如果同时有多个线程更新同一数据库,这种方法需要在应用程序级别进行同步,对吗? - Dikla

1
你可以将双列主键设置为唯一,并将自增键设为主键

0
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内部,并将第二个主键的值设为自增序列。
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;

我不明白这与问题有什么关系。 - Eaton Emmerich
@EatonEmmerich 用户想要的只是基于两列开始的序列,这在Innodb引擎中是不可能的。因此建议使用json的方式来实现。 - Kanagavelu Sugumar
这些 SQL 查询会在 InnoDB 上运行吗? - Eaton Emmerich
1
@EatonEmmerich 是的。 - Kanagavelu Sugumar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接