14

InnoDB的自增键和row_id用完了会发生什么?

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzIxNTQ3NDMzMw%3D%3D&%3Bmid=2247484391&%3Bidx=1&%3Bsn=d3000520fc5917e59bf7b0b85f9afc67
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

自增键用完了会发生什么?

我们在建表的时候为某个索引列( 注意:必须是索引列 )添加 AUTO_INCREMENT 属性,就像这样:

CREATE TABLE t (
    c1 TINYINT AUTO_INCREMENT,
    c2 TINYINT,
    KEY idx_c1 (c1)
) ENGINE=InnoDB;

t 中包含一个索引列 c1 ,该列被添加了 AUTO_INCREMENT 属性。我们先向该表中插入一条记录:

mysql> INSERT INTO t(c1, c2) VALUES(126, 1);
Query OK, 1 row affected (0.02 sec)

之后我们不再在 INSERT 语句中显式地插入 c1 列的值,它的默认值就将是从当前插入的最大的那个值之后自增,比如这样:

mysql> INSERT INTO t(c2) VALUES(1);
Query OK, 1 row affected (0.01 sec)

我们看一下此时表 t 中的数据:

mysql> SELECT * FROM t;
+-----+------+
| c1  | c2   |
+-----+------+
| 126 |    1 |
| 127 |    1 |
+-----+------+
2 rows in set (0.02 sec)

因为 c1 列是 TINYINT 类型的,使用1个字节存储数据,它能存储最大的值就是 127 ,如果当该列的值到达 127 之后,我们继续向表中插入数据,自增列 c1 的值将会变成什么呢?

mysql> INSERT INTO t(c2) VALUES(1);
Query OK, 1 row affected (0.01 sec)

插入成功之后我们再看一下表中的数据:

mysql> SELECT * FROM t;
+-----+------+
| c1  | c2   |
+-----+------+
| 126 |    1 |
| 127 |    1 |
| 127 |    1 |
+-----+------+
3 rows in set (0.01 sec)

很显然,自增列 c1 的值将不再继续增长,而是取的 TINYINT 所能存储的最大值。

这里需要注意的是,在当前举的例子中,我们只是在自增列 c1 上边建立了一个普通的二级索引 idx_c1 ,所以键值重复也没啥问题。不过我们一般将 AUTO_INCREMENT 属性应用在表的主键上,此时如果自增列值达到了主键对应数据类型所能存储的最大值时,就会报错(因为主键值重复),大家一定注意!

row_id用完了会发生什么?

在我们使用 InnoDB 存储引擎来建表时,如果我们自己没有显式地创建主键时,存储引擎会默认找一个具有 NOT NULL 属性的唯一二级索引列来充当主键,如果我们在建表语句中也没有写具有 NOT NULL 属性的唯一二级索引列,那很抱歉,存储引擎默认会为我们添加一个称之为 row_id 的主键列。

这个 row_id 列默认是6个字节大小,值得注意的是,设计 InnoDB 的大叔并不是为每一个用户未显式创建主键的表的 row_id 列都单独维护一个计数器,而是所有的表都共享一个全局的计数器。比方说我们没有对表 t1 t2 显式创建主键,存储引擎为它们都创建了一个 row_id 列,如果我们向表 t1 中插入了一条记录,那么就从全局计数器里分配一个值做该表 row_id 列的值,然后将全局计数器加1;接着我们再向表 t2 中插入一条记录,那么就再从全局计数器里分配一个值做该表 row_id 列的值,然后将全局计数器加1。

有很多同学有疑惑,如果这个全局计数器的值超过了6个字节所能表示的最大值时,会发生什么,全局计数器从0重新开始技术,一切从头再来么?

哈哈:smile:,并不会这样。虽然 row_id 由6个字节组成,但是设计 InnoDB 的大叔却是使用8个字节存储全局计数器的值,他们将这8个字节分两次写入 row_id ,第一次写入右边四个字节到 row_id 的右边4个字节,接着将左边四个字节再写入 row_id 的左边的2个字节,就像这样:

meuM3qA.jpg!web

在将全局计数器左边四个字节再写入 row_id 的左边的2个字节 时采用如下函数:

UNIV_INLINE
void
mach_write_to_2(
/*============*/
	byte*	b,	/*!< in: pointer to two bytes where to store 也就是row_id值前2个字节所在的内存地址*/
	ulint	n)	/*!< in: ulint integer to be stored 也就是全局计数器的左4个字节值*/
{
	ut_ad(b);
	ut_ad((n | 0xFFFFUL) <= 0xFFFFUL);

	b[0] = (byte)(n >> 8);
	b[1] = (byte)(n);
}

可以看到代码中有这样一行:

ut_ad((n | 0xFFFFUL) <= 0xFFFFUL);

这是一个断言函数,这行代码的意思就是全局计数器的左边4个字节值 n 必须不大于2个字节所能存储的最大值,否则的话断言就失败了,然后MySQL就挂掉了,就挂掉了,就挂掉了~

也就是说如果 row_id 用完了之后MySQL就会挂掉,那种程序直接退出的挂掉~ 不过6个字节已经足够大了,大家可以算算如果想让MySQL挂掉需要插入多少条记录呢?

小青蛙历史文章(历史文章,不容错过):

小青蛙2019年原创文章集锦

如此重要的能力,可惜大部分人没有

容易被忽视的MySQL字符集问题?

关于事务和锁的一些细节

个人所得税涨了,日子又拮据了一点

补数到底是个什么玩意儿?从根儿上理解一下

MySQL小册创作之心路历程

虚拟内存是个啥 | 一分钟系列

MySQL中IS NULL、IS NOT NULL、!=不能用索引?胡扯!

长按关注小青蛙,都是干货喔

uYNFnev.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK