「MySQL」 - 自增主键id

MySQL里有很多自增的id,每个自增id 都是定义了初始值,然后不停地往上加步长。虽然自然数是没有上限的,但是在计算机里,只要定义了表示这个数的字节长度,那它就有上限。

一、AUTO_INCREMENT

CREATE TABLE `dvd_category` (
  `dvd_category_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '分类ID',
  `dvd_category_name` varchar(64) DEFAULT NULL COMMENT '分类名称',
  `dvd_category_payment` decimal(10,2) NOT NULL COMMENT '每日租赁费用',
  `dvd_category_status` int(4) NOT NULL DEFAULT '0' COMMENT '分类状态,0-未启用、1-启用',
  `created_date` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '创建日期',
  `last_modified_date` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '最后一次修改日期',
  PRIMARY KEY (`dvd_category_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4294967295 DEFAULT CHARSET=utf8;

测试库找了一张包含自增主键的表,通过设定AUTO_INCREMENT的值,重新建表。将自增id设置为2^32-1,也即UINT最大值。

INSERT INTO `dvd_db`.`dvd_category` 
(`dvd_category_name`, `dvd_category_payment`, `dvd_category_status`) 
VALUES ('Movies', '20.00', '1');

插入一条数据,表中AUTO_INCREMENT没有改变(还是4294967295),会导致了后续的INSERT语句拿到相同的自增id值,再试图执行插入语句,报主键冲突错误。

2^32-1(4294967295)并不是一个特别大的数,对于一个频繁插入删除数据的表来说,是会被用完的。建表时如果有需要,应该创建成8字节的bigint unsigned。

二、InnoDB自增row_id

InnoDB表如果没有指定主键,InnoDB会创建一个不可见的,长度为6个字节的row_id。InnoDB维护了一个全局的dict_sys.row_id值,所有无主键的InnoDB表,每插入一行数据,都将当前的dict_sys.row_id值作为要插入数据的row_id,然后把dict_sys.row_id的值加1。

实际上,代码实现中row_id是一个长度为8字节的无符号长整型(bigint unsigned)。但是,InnoDB在设计时,给row_id留的只是6个字节的长度,row_id能写到数据表中的值,有两个特征:

  1. row_id写入表中的值范围,是从0到2^48-1;
  2. 当dict_sys.row_id=2^48时,如果再有插入数据的行为来申请row_id,拿到以后再取最后6个字节的为0。

PS:写入表的row_id是从0开始到2^48-1。达到上限后,下一个值就是 0,然后继续循环。

如果一个MySQL实例跑得足够久的话,是有可能达到2^48-1这个上限的。在InnoDB逻辑中,申请到row_id=N后,就将这行数据写入表中;如果表中已经存在row_id=N的行,新写入的行就会覆盖原有的行(通过gdb可以验证)。

CREATE TABLE `t` (
  `id` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

gdb -p 21581 -ex 'p dict_sys.row_id=1' --batch 
# 插入五条数据,12345
INSERT INTO t VALUES (1);

SELECT * FROM t;
+------+ | id   |
+------+ |    5 |
|    2 |
|    3 |
|    4 |
|    1 |
+------+ 5 rows in set (0.00 sec)

gdb -p 21581 -ex 'p dict_sys.row_id=281474976710655' --batch 
# 插入五条数据,1020304050
INSERT INTO t VALUES (10);

SELECT * FROM t;
+------+ | id   |
+------+ |   20 |
|   30 |
|   40 |
|   50 |
|    4 |
|    5 |
|   10 |
+------+ 7 rows in set (0.00 sec)

如上述结论,之前的数据被新的INSERT覆盖。所以需要在InnoDB表中主动创建自增主键,而不是依靠系统提供的id。

数据库系统作为一个7*24小时全年无休的服务,设计表时是需要考虑数据量以及边界的。

对于分布式的自增id,以后进行总结。

参考:

「极客时间」-《MySQL实战45讲》-《第45讲 | 自增id用完怎么办》 – 2019-02-25 林晓斌

    原文作者:isisiwish
    原文地址: https://zhuanlan.zhihu.com/p/61677354
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞