MySQL 案例:自增列的空洞问题与“小”BUG
前言
在 MySQL 的常见规范里面,每个表都要设置主键,一般来说都会推荐自增列作为主键,这和 MySQL 属于聚簇索引表有关,顺序增长的主键比较合适。而自增列中比较常遇见的问题就是自增列的空洞。原生的 MySQL 自增列也存在一个 BUG,可能会影响到数据一致性,本文也会详细介绍,在自建 MySQL 的时候尽量不要踩到这个坑。
空洞问题
问题介绍
自增列的空洞一般指的就是自增列不是连续增长,中间出现一些数值上的断层。比如手动写入自增列的值,就有可能会出现这种现象:
mysql> insert into t1 values(1,1),(3,3),(5,5);
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql>
mysql>
mysql> select * from t1;
+----+------+
| id | a |
+----+------+
| 1 | 1 |
| 3 | 3 |
| 5 | 5 |
+----+------+
3 rows in set (0.00 sec)
mysql> show create table t1;
+-------+---------------------------------------------------------------------------+
| Table | Create Table |
+-------+---------------------------------------------------------------------------+
| t1 | CREATE TABLE `t1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 |
+-------+---------------------------------------------------------------------------+
1 row in set (0.00 sec)
可以看到表只有三行数据,但是自增列的值已经变到了 6,“浪费”了两个值。当浪费的值过多的时候,就可能会出现如下的问题:
mysql> insert into t1 values(2147483647,0);
Query OK, 1 row affected (0.01 sec)
mysql> show create table t1;
+-------+--------------------------------------------------------------------------+
| Table | Create Table |
+-------+--------------------------------------------------------------------------+
| t1 | CREATE TABLE `t1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2147483647 DEFAULT CHARSET=utf8mb4 |
+-------+--------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> insert into t1 values(null,0);
ERROR 1062 (23000): Duplicate entry '2147483647' for key 'PRIMARY'
mysql>
自增列如果浪费过多,即使是 int 也很可能会用完,然后再尝试插入数据的时候就会报错了。
问题的监控
自增列的监控,在 5.7 版本(低于 5.7 的还是尽快升级上来吧)是比较方便的,可以在 sys 视图中直接查询视图
mysql> select * from sys.schema_auto_increment_columnsG
*************************** 1. row ***************************
table_schema: test
table_name: t1
column_name: id
data_type: int
column_type: int(11)
is_signed: 1
is_unsigned: 0
max_value: 2147483647
auto_increment: 2147483647
auto_increment_ratio: 1.0000
*************************** 2. row ***************************
table_schema: sbtest
table_name: sbtest1
column_name: id
data_type: int
column_type: int(11)
is_signed: 1
is_unsigned: 0
max_value: 2147483647
auto_increment: 10000001
auto_increment_ratio: 0.0047
2 rows in set (0.04 sec)
mysql>
通过比较 max_value,auto_increment,auto_increment_ratio 这三个值很容就能监控到即将出现问题的自增列。真的可能会出现问题的时候,就需要及时在业务维护窗口来调整自增列的数据类型,使用 unsigned 或者是 bigint。
问题的避免
如何避免这个问题,涉及到 MySQL 业务实际使用的 insert 语句的类型,这里推荐阅读(官方文档),里面有详细的分析和场景解析。
简单来说,自增列的值有一个特点:一旦被使用之后,是不会被 rollback 的,因此当各类 insert 操作被回滚之后,自增列的值就被“浪费了”。实际上这个 rollback 指代的不仅仅是回滚,而是指那些拿到了自增列值,但是没有实际 insert 数据的场景。
因此除了 rollback 事务的场景以外,需要特别关注的就是 REPLACE 和 INSERT…ON DUPLICATE KEY UPDATE,因为这两个操作会获取自增列的值,但是经常不会触发 insert,而是 update。
BUG
关于这个 BUG,其实也不好说是 BUG 还是“特性”。现象是 MySQL 在 5.7 和 5.7 之前,自增列的值是保存在内存中的,这就导致了 MySQL 重启之后会丢失这个自增列的值,所以每次重启之后,MySQL 会把表的自增列的值重置为自增列的 MAX VALUE。实践一下看看效果:
mysql> truncate table t1;
Query OK, 0 rows affected (0.03 sec)
mysql> insert into t1 values(1,1),(3,3),(5,5),(7,7);
Query OK, 4 rows affected (0.01 sec)
Records: 4 Duplicates: 0 Warnings: 0
mysql> show create table test.t1;
+-------+----------------------------------------------------------------------------+
| Table | Create Table |
+-------+----------------------------------------------------------------------------+
| t1 | CREATE TABLE `t1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 |
+-------+---------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> delete from t1 where id = 7;
Query OK, 1 row affected (0.01 sec)
mysql> show create table test.t1;
+-------+--------------------------------------------------------------------------+
| Table | Create Table |
+-------+--------------------------------------------------------------------------+
| t1 | CREATE TABLE `t1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 |
+-------+--------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> exit
Bye
root@VM-56-136-debian:~# service mysql restart
root@VM-56-136-debian:~# mysqllocal
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or g.
Your MySQL connection id is 2
Server version: 5.7.31-log MySQL Community Server (GPL)
Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or 'h' for help. Type 'c' to clear the current input statement.
mysql> show create table test.t1;
+-------+---------------------------------------------------------------------------+
| Table | Create Table |
+-------+---------------------------------------------------------------------------+
| t1 | CREATE TABLE `t1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 |
+-------+---------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql>
可以看到原生的 MySQL 在重启之后自增列的值被重置了。这个现象被称之为 BUG 主要是在现在的互联网业务中,支撑业务数据的不仅仅只有 MySQL,还可能会有 Redis,RabbitMQ 等缓存和消息队列或者是单独的 MySQL 日志归档库,自增列可能会被用来作为关联各个存储之间的“逻辑外键”,当 MySQL 重启之后,新写入的数据可能会用到已经被删除的值,导致数据库中的数据和外部系统之间的数据出现错误的关联。另外一种问题场景就是 MySQL 自身各个表之间有外键关系,但是没有建立外键约束,也会遇到类似的问题。
这个 BUG 在 MySQL 8.0 修复了,在 8.0 中,MySQL 把这个值进行了持久化,重启之后也不会被重置。当然,腾讯云数据库 MySQL 也修复了这个问题,可以放心使用~
总结一下
自增列虽然已经长时间,大范围的使用过了,但是也要注意这类技术可能会存在一些隐藏的问题,防微杜渐,保障业务安全运行是 DBA 应尽的职责。
- Disruptor-NET和内存栅栏
- 我们是如何将 Cordova 应用嵌入到 React Native 中
- ADO.NET的弹性连接控制[ADO.NET idle connection resiliency]
- ASP.Net MVC 5 in Xamarin Studio 5.2
- 自制基于 Snips 和 Snowboy 的智能音箱来保护你的隐私
- 从 React 将从 BSD 改 MIT 许可证,谈如何选择正确的开源许可
- Topshelf 支持Mono 扩展Topshelf.Linux
- 如何在 React Native 实现类微信小程序平台:WebView 调用原生组件
- 如何运营一个开源项目并取得较大影响力?
- ASP.NET Identity 2新增双重认证、帐号锁定、防伪印章功能并修复了一些bug
- Serverless 应用开发指南:基于 Serverless 与 Lambda 的微信公共平台
- Serverless 应用开发指南:serverless 的 hello, world
- 通过一组RESTful API暴露CQRS系统功能
- 通过使用结构化数据 JSON-LD,我为网站带来了更多的流量
- MySQL 教程
- MySQL 安装
- MySQL 管理与配置
- MySQL PHP 语法
- MySQL 连接
- MySQL 创建数据库
- MySQL 删除数据库
- MySQL 选择数据库
- MySQL 数据类型
- MySQL 创建数据表
- MySQL 删除数据表
- MySQL 插入数据
- MySQL 查询数据
- MySQL where 子句
- MySQL UPDATE 查询
- MySQL DELETE 语句
- MySQL LIKE 子句
- mysql order by
- Mysql Join的使用
- MySQL NULL 值处理
- MySQL 正则表达式
- MySQL 事务
- MySQL ALTER命令
- MySQL 索引
- MySQL 临时表
- MySQL 复制表
- 查看MySQL 元数据
- MySQL 序列 AUTO_INCREMENT
- MySQL 处理重复数据
- MySQL 及 SQL 注入
- MySQL 导出数据
- MySQL 导入数据
- MYSQL 函数大全
- MySQL Group By 实例讲解
- MySQL Max()函数实例讲解
- mysql count函数实例
- MYSQL UNION和UNION ALL实例
- MySQL IN 用法
- MySQL between and 实例讲解
- [已解决]报错: ["Department":"??????????","Addr":"?????????????690???????"]
- 例16数字判断
- selector的使用
- Python获取xml数据
- Scrapy项目实战:爬取某社区用户详情
- 获取后缀名的文件
- [未解决]报错:requests post请求无法返回数据
- AkShare-股票数据-注册制审核
- tkinter的简单应用
- [已解决]报错: airtest PermissionError: [Errno 13] Permission denied
- LeetCode SQL
- 燕十八mysql复习
- 15.selenium_case04
- 16.ajax_case01
- 1.Python GUI之tkinter介绍