MySQL 自增列使用上的一些 “坑”

news2025/4/17 3:21:00

文章目录

    • 前言
    • 1. 自增列空洞
      • 1.1 手动指定
      • 2.2 分配未使用
    • 2. 自增列监控
      • 2.1 sys 库监控
      • 2.2 通用查询
    • 3. 一些 BUG
      • 3.1 重启失效
      • 3.2 冲突问题

前言

MySQL 的规范中,一般都会建议表要有主键,常使用自增列作为主键字段,这和 MySQL 属于聚簇索引表有关,顺序增长的主键比较合适。最近有研发咨询,为什么有张表的自增主键变的非常大?而且偶尔还出现 Duplicate entry '4' for key 'PRIMARY' 的异常,这篇文章将介绍此类问题。

1. 自增列空洞

1.1 手动指定

自增列的空洞一般指的就是自增列不是连续增长,中间出现一些数值上的断层,这种情况常发生在手动指定自增列的值,请看下面的 case:

-- 创建一张测试表,id 为自增主键
create table t1(
    id bigint auto_increment primary key,
    c1 varchar(10) not null,
    c2 varchar(10) not null
);

插入测试数据:

insert into t1(c1, c2) VALUES ('a', 'b'),('a', 'b'),('a', 'b'),('a', 'b'),('a', 'b');
idc1c2
1ab
2ab
3ab
4ab
5ab

此时再插入手动 ID 插入数据:

insert into t1 value (10, 'a', 'b');

再查询 t1 表的自增值:

select TABLE_NAME, AUTO_INCREMENT from information_schema.TABLES where TABLE_NAME = 't1';
TABLE_NAMEAUTO_INCREMENT
t111

查询表中的数据:

idc1c2
1ab
2ab
3ab
4ab
5ab
10ab

此时可以看到表中的数据只有 6 行,但是自增值已经变为 6,浪费了中间四个值,如果为主键字段为 INT 类型,浪费过多会导致字段溢出,插入数据时就会报错,所以规范里也提到使用 bigint 类型作为表的主键。
请添加图片描述

2.2 分配未使用

自增列有一个特点,就是一旦分配后,就无法被 rollback 相当于被 “浪费”,请看下方 case:

-- 创建一张测试表,id 为自增主键
create table t1(
    id bigint auto_increment primary key,
    c1 varchar(10) not null,
    c2 varchar(10) not null
);

开启事务,插入一条数据,然后回滚:

-- 开启事务
begin;
-- 插入一条记录
insert into t1(c1, c2) value('a', 'b');
-- 回滚
rollback;
-- 再次插入数据
insert into t1(c1, c2) value('a', 'b');
idc1c2
2ab

可以看到自增列是从 2 开始分配的,相当于第一个事务拿到了自增值,但是没有实际使用。

除了 Rollback 会产生分配未使用的情况外,还有 REPLACEINSERT…ON DUPLICATE KEY UPDATE 在特定情况下也会出现分配未使用的情况:

-- 创建测试表,注意 c1 字段有唯一索引
create table t1
(
    id bigint auto_increment,
    c1 varchar(10) not null,
    c2 varchar(10) not null,
    PRIMARY KEY (`id`),
    UNIQUE KEY uqk_c1(c1)
);

插入测试数据:

insert into t1(c1, c2) VALUES ('a', 'b'),('b', 'b');

使用 REPLACE 语句:

-- 如果有 c1 = a 将 c2 修改为 w
replace into t1(c1, c2) value ('a', 'w');
-- 上面已经修改了值,下面执行后数据没有实际变化
replace into t1(c1, c2) value ('a', 'w');
replace into t1(c1, c2) value ('a', 'w');
replace into t1(c1, c2) value ('a', 'w');
replace into t1(c1, c2) value ('a', 'w');
replace into t1(c1, c2) value ('a', 'w');

此时表中只有两条记录:

idc1c2
2bb
8aw

再看该表的 AUTO_INCREMENT 值,已经增长到 9 相当于产生了空洞:

select TABLE_NAME, AUTO_INCREMENT from information_schema.TABLES where TABLE_NAME = 't1';
TABLE_NAMEAUTO_INCREMENT
t19

除此之外,还有 DUPLICATE KEY UPDATE 语法也会有此类情况。这两个操作会获取自增列的值,但是经常不会触发 insert,而是 update。

insert into t1(c1, c2) value ('a', 'b') on duplicate key update c2 = 'vvvv';

2. 自增列监控

2.1 sys 库监控

为了防止自增列的值 “用光”,导致业务报错。可以使用下方 SQL 监控自增列:

select * from sys.schema_auto_increment_columns;

2.2 通用查询

如果你的数据库不支持使用这条 SQL(版本问题,或者有些云厂商没有开放 sys 库)可以使用下方 SQL:

SELECT table_schema,
       table_name,
       column_name,
       c.COLUMN_TYPE,
       AUTO_INCREMENT,
       POW(2, CASE data_type
                  WHEN 'tinyint' THEN 7
                  WHEN 'smallint' THEN 15
                  WHEN 'mediumint' THEN 23
                  WHEN 'int' THEN 31
                  WHEN 'bigint' THEN 63
                  END + (column_type LIKE '% unsigned')) - 1 AS max_int
FROM information_schema.tables t
         JOIN information_schema.columns c USING (table_schema, table_name)
WHERE c.extra = 'auto_increment'
  and c.COLUMN_KEY = 'PRI'
  AND t.TABLE_SCHEMA NOT IN ('information_schema', 'mysql', 'sys', 'performance_schema')
  AND t.auto_increment IS NOT NULL;

3. 一些 BUG

3.1 重启失效

MySQL 5.7 版本 auto_increment 是存储在内存中的,这就导致每次重启 MySQL 都会重新计算该值,计算逻辑是取该字段的 MAX VALUE 请看下方 case:

-- 创建一张测试表,id 为自增主键
create table t1(
    id bigint auto_increment primary key,
    c1 varchar(10) not null,
    c2 varchar(10) not null
);

插入 8 行记录:

insert into t1(c1, c2) value ('a', 'b'),('a', 'b'),('a', 'b'),('a', 'b'),('a', 'b'),('a', 'b'),('a', 'b'),('a', 'b');

此时 AUTO_INCREMENT 的值为 9,下一条写入会被分配自增 ID 为 9:

select TABLE_NAME, AUTO_INCREMENT from information_schema.TABLES where TABLE_NAME = 't1';
TABLE_NAMEAUTO_INCREMENT
t19

然后删除后 4 条记录:

delete from t1 where id > 4;

重启数据库:

>$ service mysqld restart
Shutting down MySQL.... SUCCESS! 
Starting MySQL... SUCCESS! 

查询 t1 表的自增 ID:

select TABLE_NAME, AUTO_INCREMENT from information_schema.TABLES where TABLE_NAME = 't1';
TABLE_NAMEAUTO_INCREMENT
t15

可以看到 MySQL 在重启之后自增列的值被重置了。这个现象被称之为 BUG 主要是在现在的互联网业务中,支撑业务数据的不仅仅只有 MySQL,还可能会有 Redis,RabbitMQ 等缓存和消息队列或者是单独的 MySQL 日志归档库,自增列可能会被用来作为关联各个存储之间的“逻辑外键”,当 MySQL 重启之后,新写入的数据可能会用到已经被删除的值,导致数据库中的数据和外部系统之间的数据出现错误的关联。另外一种问题场景就是 MySQL 自身各个表之间有外键关系,但是没有建立外键约束,也会遇到类似的问题。

该 BUG 已在 MySQL 8.0 版本修复。

3.2 冲突问题

当自增列被修改过时,可能会出现 Duplicate entry '4' for key 'PRIMARY' 的异常,详细看下方 case:

-- 创建一张测试表,id 为自增主键
create table t1(
    id bigint auto_increment primary key,
    c1 varchar(10) not null,
    c2 varchar(10) not null
);

插入测试数据:

insert into t1(c1, c2) VALUES ('a', 'b'),('a', 'b'),('a', 'b'),('a', 'b'),('a', 'b');

修改自增列的值:

update t1 set id = 6 where id = 1;

-- 表中数据:
+----+----+----+
| id | c1 | c2 |
+----+----+----+
|  2 | a  | b  |
|  3 | a  | b  |
|  4 | a  | b  |
|  5 | a  | b  |
|  6 | a  | b  |
+----+----+----+

再尝试获取自增值插入:

insert into t1(c1, c2) VALUES ('a', 'b');

ERROR 1062 (23000): Duplicate entry ‘6’ for key ‘PRIMARY’

这个 case 报错的原因是 UPDATE 修改了自增列的值,但是 UPDATE 不会触发自增值的分配,却占用了一个未来要被分配的自增值,当该值需要被分配出来的时候,由于被 UPDATE 修改的行占用了,导致 SQL 报错,所以主键(自增列)尽量不要去修改。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/601662.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【FlatpanelsHD】HDR生态系统追踪器

Dolby Vision被称为Profile 8.4,与基于pq(杜比实验室开发的感知量化技术,也是无处不在的HDR10的基础)的所有其他口味不同,它基于HLG或Hybrid Log Gamma,由BBC和NHK开发,主要用于电视直播。 用HLG捕捉HDR视频的相机并不…

选择自动化测试工具的主要考虑点是什么?

在软件开发生命周期中,测试是非常重要的一部分。测试的目的是确保软件系统的质量和可靠性。而随着软件开发越来越复杂,传统的手动测试方式已经无法满足测试的要求。自动化测试工具的出现就为测试工作提供了更高效、更准确的解决方案,那选择自…

基于AT89C51单片机的6位电子密码锁详细设计

点击链接获取Keil源码与Project Backups仿真图: https://download.csdn.net/download/qq_64505944/87855657?spm=1001.2014.3001.5503 源码获取 目录 1绪论 1 1.1 课题背景 1 1.2 课题设计目标 1 2系统方案论证 2 2.1 主控部分的选择 2 2.2 密码输入方式的选择 2 3 系统总体…

day8 -- 全文本搜索

brief InnoDB存储引擎从MySQL 5.6开始支持全文本搜索。具体来说,MySQL使用InnoDB存储引擎的全文本搜索功能称为InnoDB全文本搜索(InnoDB Full-Text Search)。InnoDB全文本搜索支持标准的全文本搜索查询语法和多语言分词器,因此可…

useCallback使用注意

背景 useCallback的作用时基于依赖项缓存函数,但是这个缓存时取值缓存而不是按照地址缓存,这导致了如果缓存的函数使用的值依赖外部某个变量,这个变量只会取第一次用到的值 例子 具体可以看https://codesandbox.io/s/misty-night-vds9oo?…

JavaScript:箭头函数与普通函数的区别与适用场景

文章目录 1 箭头函数与普通函数的区别1.1 语法上的区别1.2 this指向的区别1.3 arguments对象的区别1.4 箭头函数不能用作构造函数 2 箭头函数和普通函数的适用场景2.1 普通函数的适用场景2.1.1 构造函数2.1.2 方法2.1.3 回调函数 2.2 箭头函数的适用场景2.2.1 简答的回调函数2.…

IIS配置URL重写,http重定向https

文章目录 1️⃣ URL重写1.1 URL重写插件下载1.2 URL重写插件安装1.3 URL重写插件配置 优质资源分享 作者:xcLeigh 文章地址:https://blog.csdn.net/weixin_43151418/article/details/131004077 IIS配置URL重写,http重定向https,ht…

【计算机图形学】【代码复现】A-SDF中的数据集制作与数据生成

Follow A-SDF 的Data Generation部分: We follow (1) ANSCH to create URDF for shape2motion dataset (1-2) URDF2OBJ(本人认为是1-2之间需要进行的重要的过渡部分) (2) Manifold to create watertight meshes (3) and modified mesh_to_sdf…

CPLEX Studio 集成开发环境 (IDE) 介绍

CPLEX Studio 集成开发环境 (IDE) 介绍 参考B站视频:cplex入门到精通 1.CPLEX Studio IDE 实现的功能 IBM ILOG CPLEX Studio IDE 是一个用于数学规划、约束规划以及一般组合优化应用程序的集成开发环境。 它是适用于 OPL(优化编程语言)和…

PointNetGPD使用手册

1.创建环境配置环境变量 mkdir -p $HOME/code/ cd $HOME/code/ - Set environment variable PointNetGPD_FOLDER in your $HOME/.bashrc file. export PointNetGPD_FOLDER$HOME/code/PointNetGPD 2.安装 1. Install pcl-tools via sudo apt install pcl-tools. 2. An e…

在家当了几年废物,庆幸自己当初进了软件测试这行~

为什么会学习软件测试? 28岁了,仔细算一下6年了,工作了一年,没去工作就一直待在家,家里固定每个月给几千元,偶尔会都给一些,但依旧没钱,家里给我买了一套房子,出门300米…

【刷题之路Ⅱ】LeetCode 739. 每日温度

【刷题之路Ⅱ】LeetCode 739. 每日温度 一、题目描述二、解题1、方法1——暴力法1.1、思路分析1.2、代码实现 2、方法2——单调栈2.1、思路分析2.2、先将栈实现一下2.3、代码实现 一、题目描述 原题连接: 739. 每日温度 题目描述: 给定一个整数数组 tem…

ChatGPT 使用 拓展资料:吴恩达大咖 基于LangChain的LLM应用程序开发-1

ChatGPT 使用 拓展资料:吴恩达大咖 基于LangChain的LLM应用程序开发 基于LangChain的LLM应用程序开发 LangChain for LLM Application Development [https://www.deeplearning.ai/short-courses/langchain-for-llm-application-development/] 基于LangChain的LLM应用程序开发…

干货|SpringCloud之注册中心如何选用

SpringCloud的框架并不陌生了,在业内微服务领域的扛把子。今天来看一看如何根据业务需要,来选择合适的注册中心? 注册中心是微服务管理节点通信、核心配置的关键组件,从分布式多节点的前提下最主要要解决是就是分布式下的一致性问…

教你领取免费的亚马逊云服务服务器并搭建服务器环境的方法教程

本篇文章主要讲解,亚马逊新用户注册领取亚马逊免费服务器的详细操作流程方法,以及如何规避免费服务器到期后自动续费的问题解决办法。 作者:任聪聪 日期:2023年6月2日 前提材料准备 1.需要先准备好你的亚马逊账号注册所需的手机号、邮箱、vi…

18-Vue3中一些新的组件

目录 1、Fragment2、Teleport3、Suspense 1、Fragment 在Vue2中: 组件必须有一个根标签在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中好处: 减少标签层级, 减小内存占用 2、Teleport 什么是Teleport?—— Teleport 是一种能够将我…

一个开源的基于golang开发的企业级物联网平台

SagooIOT是一个基于golang开发的开源的企业级物联网基础开发平台。负责设备管理和协议数据管理,支持跨平台的物联网接入及管理方案,平台实现了物联网开发相关的基础功能,基于该功能可以快速的搭建起一整套的IOT相关的业务系统。旨在通过可复用…

测试4年,费时8个月,入职阿里,涨薪14K,可算是熬出头了····

前言 你的努力,终将成就无可替代的自己 本科毕业后就一直从事测试的工作,和多数人一样,最开始从事功能测试(所谓的点点点)的工作,看着自己的同学一步一步往上走,自己还是在原地踏步,…

java打jar包并包装成exe解压即用

1首先找到要加载的main方法类 public static void main(String[] args) { //创建该对象则调用构造方法,对象实现ActionListener则自动调用actionPerformed()方法new PicdealMain();}2.点击 idea:File->Project Struce…(快捷键…

MyBatis深入学习总结(1.0)

MyBatis总结 MyBatis入门操作 简介 原始jdbc操作(查询数据) 原始jdbc操作(插入数据) 原始jdbc操作的分析 原始jdbbc开发存在的问题如下: 数据库连接创建、释放频繁造成系统资源的浪费从而影响系统性能sql语句在代…