以下内容基于MySql8.0进行讲解。
建表时设置主键自增并指定自增起始值
CREATE TABLE `user` (
`id` int NOT NULL PRIMARY KEY AUTO_INCREMENT,
`name` varchar(30) DEFAULT NULL comment '姓名',
`age` int DEFAULT NULL comment '年龄',
UNIQUE KEY `uniq_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT = 100;
说明:
AUTO_INCREMENT
:指定该列自增AUTO_INCREMENT = 100
:指定自增起始值为100,默认起始值为1,每次自增1如果数据表已经存在,可以使用 ALTER TABLE 语句修改表结构并设置自增主键。例如,将 user表中的 id 字段设置为自增主键,可以使用如下 SQL 语句:
ALTER TABLE user MODIFY id INT AUTO_INCREMENT PRIMARY KEY;
将AUTO_INCREMENT 从字段类型中删除即可删除掉自增约束
alter table user MODIFY id INT PRIMARY KEY;
MySQL提供了主键自增机制AUTO_INCREMENT,对主键使用,保证主键的唯一性,默认值起始值为1,每次增长量为1,也就是自增起始值auto_increment_offset 和自增步长 auto_increment_increment。
# 查询mysql是否支持自增长,查询自增长步长,自增长起始值
mysql> show variables like '%auto_increment%';
+-------------------------|-------+
| Variable_name | Value |
+-------------------------|-------+
|auto_increment_increment | 1 |
+-------------------------|-------+
|auto_increment_offset | 1 |
+-------------------------|-------+
说明:
表的结构定义存放在后缀名为.frm的文件中,但是并不会保存自增值。不同的引擎对于自增值的存储策略不同。
AUTO_INCREMENT 约束的字段只能是整数类型(TINYINT、SMALLINT、INT、BIGINT 等)。
错误演示
create table employee(
eid int auto_increment,
ename varchar(20)
);
# ERROR 1075 (42000): Incorrect table definition; there can be only one auto column and it must be defined as a key
create table employee(
eid int primary key,
ename varchar(20) unique key auto_increment
);
# ERROR 1063 (42000): Incorrect column specifier for column 'ename' 因为ename不是整数类型
在某些情况下自增字段的值的不连续的,如果你不知道有这个问题可能会踩坑。
下面我们通过一个实例分析自增字段的值为什么不连续。
以下列举几个自增不连续的示例。
CREATE TABLE `user` (
`id` int NOT NULL PRIMARY KEY AUTO_INCREMENT,
`name` varchar(30) DEFAULT NULL comment '姓名',
`age` int DEFAULT NULL comment '年龄',
UNIQUE KEY `uniq_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT = 1;
向user表中插入一条数据,sql如下:
insert into user values(1,'张三',1);
此时,表 user 中已经有了(1,‘张三’,1)这条记录,这时再执行一条插入数据命令:
mysql> INSERT INTO user VALUES(null,'张三',1);
ERROR 1062 (23000): Duplicate entry '张三' for key 'user.uniq_name'
由于表中已经存在 name=1 的记录,所以报 Duplicate key error(唯一键冲突)。在这之后,再插入新的数据时,自增 id 就是 3,这样就出现了自增字段值不连续的情况。
解释:MySQL对于递增值的使用是一次性的,那么第二次执行插入时,不管语句成功还是失败,那么这个递增值就会浪费掉。
在执行insert into user values(null,‘张三’,1);时虽然唯一索引冲突了,但是自增id已经自增了一个,内存中自增id已经用到了2,所以下一次插入时id会再次自增变成了3。
为了保证主键id的唯一性,在申请自增id时,MySQL会对申请操作加锁。一般情况下,这个申请动作会很快。
对于一般的批量插入,比如insert into … values(xxx),由于插入的Value个数可以提前计算得出,MySQL会一次性的申请足够数量的id,以保证性能。
但是对于insert into … select 这种语句就有点麻烦了,由于无法确定到底需要申请多个主键id,如果插入一条申请一个的话,假设要插入100万条记录,那就得申请100万次,可想而知性能会有多么差劲。
批量插入数据,包含的语句类型是 insert…select、replace…select 和 load data 语句。
所以对于这种批量插入的语句,MySQL采用了一种翻倍申请的优化策略:
CREATE TABLE `t` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `c` (`c`)
) ENGINE=InnoDB;
insert into t values(null, 1,1);
insert into t values(null, 2,2);
insert into t values(null, 3,3);
insert into t values(null, 4,4);
create table t2 like t;
insert into t2(c,d) select c,d from t;
insert into t2 values(null, 5,5);
由于这种规则,t2表的批量插入就只用到了 id=1 到 id=4,id=8,而id=5 到id=7就被浪费掉了,因此自增主键的批量申请也会导致自增主键出现不连续的情况。
1. 删除记录导致的不连续
当我们删除表中的记录时,MySQL的自增主键并不会重置。下次插入记录时,自增主键会从上一次插入的最大主键值加1开始递增。如果删除了表中的某些记录,那么主键的值就会跳跃,导致不连续。
2. 回滚操作导致的不连续
MySQL的自增主键是基于事务的,如果在事务中插入了记录,但是事务回滚了,那么自增主键的值也会回滚,导致不连续。
3. 唯一键冲突导致的不连续
若有唯一索引冲突,则会导致自增主键的不连续。MySQL对于递增值的使用是一次性的,那么第二次执行插入时,不管语句成功还是失败,那么这个递增值就会浪费掉。
4.MySQL5.7及以前的版本重启导致不连续
MySQL5.7及以前的版本InnoDB存储引擎不支持自增值持久化,重启导致自增值丢失。
5.批量插入会造成主键不连续
为了保证主键id的唯一性,在申请自增id时,MySQL会对申请操作加锁。一般情况下,这个申请动作会很快。
对于一般的批量插入,比如insert into … values(xxx),由于插入的Value个数可以提前计算得出,MySQL会一次性的申请足够数量的id,以保证性能。
但是对于insert into … select 这种语句就有点麻烦了,由于无法确定到底需要申请多个主键id,如果插入一条申请一个的话,假设要插入100万条记录,那就得申请100万次,可想而知性能会有多么差劲。
所以对于这种批量插入的语句,MySQL采用了一种翻倍申请的优化策略:
语句执行时,第一次申请一个自增id,第二次申请2个自增id,第三次申请4个自增id…
即每次申请的数量都比上次多一倍,这样虽然会浪费一些自增id,但是可以保证插入的效率,从性能角度来看,是可以接受的。
1. 使用TRUNCATE TABLE重置自增主键
如果希望将自增主键重置为连续的递增序列,可以使用TRUNCATE TABLE语句删除表中的所有记录,并重置自增主键的值。例如:
TRUNCATE TABLE table_name;
需要注意的是,TRUNCATE TABLE语句会删除表中的所有记录,并且无法回滚。
2. 使用ALTER TABLE重置自增主键
ALTER TABLE语句可以用来修改表的结构,包括重置自增主键。可以使用以下语句将自增主键重置为指定的值:
ALTER TABLE table_name AUTO_INCREMENT = value;
其中,table_name是需要修改的表名,value是希望设置的自增主键值。
3. 避免手动指定主键值
为了避免因手动指定主键值而导致的主键冲突,可以遵循以下原则:
INSERT INTO table_name (column1, column2) VALUES ('value1', 'value2');
SELECT LAST_INSERT_ID();
大家可能会有点疑问,为什么自增id是一次性使用的?
其实原因也很简单,大家稍微一想就明白了。
假设有两个事务在同时执行,为了保证自增id的唯一性,MySQL会对申请动作加锁,然后两个事务各获得一个自增id。比如事务1申请到了自增id100,事务2申请到了自增id101。
当事务2成功提交,事务1因为某些原因回滚了。
如果我们要回退复用事务1的id,将AUTO_INCREMENT又设置成了100+1,那么下一个事务来申请自增id时,就会拿到101,而这时101已经被事务2用掉了,就会造成主键冲突。
当然我们也可以每次都让MySQL检查一下主键是否冲突,如果冲突就跳过这个id,但是这样一来,本来申请自增id这个很轻的动作就会变得很重,对性能的影响就会很大。
所以,从性能角度考虑,InnoDB只保证了主键id是大致递增的,而不保证是顺序递增的。
MySQL的自增主键在特定情况下可能会出现不连续的情况,包括删除记录、回滚操作和主键冲突。为了解决这个问题,我们可以使用TRUNCATE TABLE或ALTER TABLE语句来重置自增主键的值,也可以避免手动指定主键值来避免主键冲突。在实际应用中,根据具体需求选择合适的解决办法。