mysql实战45讲--- 45 MySQL自增id

45 MySQL自增id

表定义自增id

说到自增id,前面提到mysql的自增id不连续,当表定义的自增值达到上限后的逻辑是:再申请下一个id时,得到的值保持不变

复制代码

create table t(id int unsigned auto_increment primary key) auto_increment=4294967295;
insert into t values(null);
// 成功插入一行 4294967295
show create table t;
/* CREATE TABLE `t` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4294967295;
*/

insert into t values(null);
//Duplicate entry '4294967295' for key 'PRIMARY

复制代码

2^32-1 不是一个特别大的数,对于一个频繁插入删除数据的表来说,是可能用完的,因此在建表的时候需要观察这个表是否有可能达到这个上限,如果有可能,则需要建成8个字节的bigint unsigned.

复制代码

--mysql 
BIGINT[(M)] [UNSIGNED] [ZEROFILL]
A large integer. The signed range is -9223372036854775808 to 9223372036854775807. The
unsigned range is 0 to 18446744073709551615.
SERIAL is an alias for BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE 

NO_UNSIGNED_SUBTRACTION
By default, subtraction between integer operands produces an UNSIGNED result if any operand
isUNSIGNED. When NO_UNSIGNED_SUBTRACTION is enabled, the subtraction result is signed, even
if any operand is unsigned. For example, compare the type of column c2 in table t1 with that of
column c2 in table t2: 
mysql> SET sql_mode='';
mysql> CREATE TABLE test (c1 BIGINT UNSIGNED NOT NULL);
mysql> CREATE TABLE t1 SELECT c1 - 1 AS c2 FROM test;
mysql> DESCRIBE t1;
+-------+---------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra | 
+-------+---------------------+------+-----+---------+-------+
| c2 | bigint(21) unsigned | | | 0 | |
+-------+---------------------+------+-----+---------+-------+
mysql> SET sql_mode='NO_UNSIGNED_SUBTRACTION';
mysql> CREATE TABLE t2 SELECT c1 - 1 AS c2 FROM test;
mysql> DESCRIBE t2;
+-------+------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------+------+-----+---------+-------+
| c2 | bigint(21) | | | 0 | |
+-------+------------+------+-----+---------+-------+ 
mysql> SET sql_mode = '';
mysql> SELECT CAST(0 AS UNSIGNED) - 1;
+-------------------------+
| CAST(0 AS UNSIGNED) - 1 |
+-------------------------+
| 18446744073709551615 |
+-------------------------+
mysql> SET sql_mode = 'NO_UNSIGNED_SUBTRACTION';
mysql> SELECT CAST(0 AS UNSIGNED) - 1;
+-------------------------+
| CAST(0 AS UNSIGNED) - 1 |
+-------------------------+
| -1 |
+-------------------------+ 

复制代码

Innodb系统自增row_id

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

实际上,在代码实现是row_id是一个长度为8个字节的无符号长整形(bigint unsigned),但是,innodb在设计时,给row_id留的只是6个字节的长度,这样写到数据库表中时只放入了后6个字节,两个特征:

--1 row_id写入表中的范围,0到2^48-1

--2 当dict_sys.row_id=2^48时,如果再有插入数据的行为要来申请row_id,拿到以后再取后6个字节的话就是0,继续循环。

Xid

在答疑文章1中,日志和索引相关问题,介绍了redo log和binlog相配合的时候,提到了它们有一个共同的字段叫做Xid,它在mysql中是用来对应事务的

Mysql内部维护了一个全局变量global_query_id,每次执行语句的时候将它赋值给query_id,然后这个变量加1,如果当前语句是这个事务执行的第一个语句,那么mysql还会同时把query_id赋值给这个事务的xid

而global_query_id是一个纯内存变量, 重启之后就清零了,所以,在同一个数据库实例汇总,不同事务的xid也有可能相同的。

但是mysql重启后,会生成新的binlog文件,这就保证了,同一个binlog文件中,xid是唯一的。

但是如果global_query_id达到上限就会继续从0开始,从理论上讲,就会出现同一个binlog文件中出现相同的xid,因为global_query_id定义的长度是8个字节,上限是2^64-1,理论上回出现这个同一个binlog,相同的xid出现。

Innodb trx_id

Xid是有server层维护的,innodb内部使用xid为了能够在innodb事务和server之间做联系,但是,innodb的trx_id,是另外维护的,事务id。

Innodb内存维护了一个max_trx_id全局变量,每次需要申请一个新的trx_id时,就获得max_trx_id的当前值,并加1.

Innodb数据可见性的核心思想是:每一行数据都记录了更新它的trx_id,当一个事务读到一行数据的时候,判断这个数据是否可见的方法,就是通过事务的一致性视图与这行数据的trx_id进行对比

对于正在执行的事务,可以通过information_schema.innodb_trx看到trx_id

([email protected]:3306) [test]> select trx_id,trx_mysql_thread_id from information_schema.innodb_trx;

 

SESSION A

SESSION B

begin;

select * from t limit 1;

 

 

select trx_id,trx_mysql_thread_id from information_schema.innodb_trx;

insert into t values('2018-4-2',2);

 

 

select trx_id,trx_mysql_thread_id from information_schema.innodb_trx;

Session a开始是一个只读事务,对于只读事务,innodb并不会分配trx_id,trx_id就是0,但是有一个很大的数,只是显示用的,在insert的时候,事务才真正分配了trx_id

只读事务不分配trx_id

--1 可以减小事务视图里面活跃事务数组的大小,因为当前正在运行的只读事务,是不影响数据的可见性判断的,所以,在创建事务的一致性视图时,innodb就只需要拷贝只读事务的trx_id

--2 可以减少trx_id的申请次数,普通的select语句不申请trx_id,大大减少了并发事务申请trx_id的锁冲突。

max_trx_id会持久化存储,重启也不会重置为0,那么从理论上讲,只要一个mysql服务跑得足够久,就可能出现max_trx_id达到2^48-1的上限,然后从0开始的情况。

当达到这个状态后,mysql就会持续出现一个脏读的bug。事务的可见性就根据trx_id来进行比较的。

thread_id

线程id(thread_id),线程id才是mysql中最常见的一种自增id,平时在show processlist里面的第一列,就是thead_id,系统保存了一个全局变量thread_id_counter,每新建一个连接,就讲thread_id_counter赋值给这个新连接的线程变量。

thread_id_counter定义的大小是4个字节,因此达到2^32-1后,就会重置为0,然后继续增加,但是不会在show processlist中看到两个相同的thread_id。

你可能感兴趣的:(mysql实战45讲--- 45 MySQL自增id)