深入探寻mysql自增列导致主键重复问题的原因_MySQL
废话少说,进入正题。
拿到问题后,首先查看现场,发现问题表的中记录的最大值比自增列的值要大,那么很明显,当有记录进行插入时,自增列产生的值就有可能与已有的记录主键冲突,导致出错。首先想办法解决问题,通过人工调大自增列的值,保证大于表内已有的主键即可,调整后,导数据正常。问题是解决了,接下来要搞清楚问题原因,什么操作导致了这种现象的发生呢?
这里有一种可能,即业务逻辑包含更新自增主键的代码,由于mysql的update动作不会同时更新自增列值,若更新主键值比自增列大,也会导致上述现象:记录最大值比自增主键值大。但开发反馈说这张表仅仅存在load data infile操作,不会进行更新主键操作,所以这个解释行不通。继续分析,表中含有唯一约束,会不会和唯一约束有关,线下实验模拟没有重现。后来想想会不会和主备切换有关系,因为前两天做过一次主备切换。于是乎,配合主备环境作了测试,果然和主备切换有关系,一切问题的来源都清晰了。
问题发生的前置条件:
1.mysql复制基于row模式
2.innodb表
3.表含有自增主键,并且含有唯一约束
4.load data infile 采用replace into语法插入数据【遇到重复唯一约束,直接覆盖】
问题发生的原理:
1.主库遇到重复unique约束时,进行replace操作;
2.replace在主库上面实际变化为delete+insert,但binlog记录的是update;
3.备库重做update动作,更新主键,但由于update动作不会更新自增列值,导致更新后记录值大于自增列值
问题重现实验:
准备工作 |
Create table test_autoinc(id int auto_increment, c1 int,c2 varchar(100),primary key(id),unique key(c1)); insert into test_autoinc(c1,c2) values(1,'abc'); insert into test_autoinc(c1,c2) values(2,'abc'); insert into test_autoinc(c1,c2) values(3,'abcdd'); insert into test_autoinc(c1,c2) values(4,'abcdd'); insert into test_autoinc(c1,c2) values(5,'abcdd'); |
|||
1 |
操作 |
备注 |
Master |
slave |
2 |
查看自增列值 Show create table test_autoinc\G |
插入5条记录后,自增列值变为6 |
CREATE TABLE `test_autoinc` ( `id` int(11) NOT NULL AUTO_INCREMENT, `c1` int(11) DEFAULT NULL, `c2` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `c1` (`c1`) ) ENGINE=InnoDBAUTO_INCREMENT=6DEFAULT CHARSET=utf8 |
CREATE TABLE `test_autoinc` ( `id` int(11) NOT NULL AUTO_INCREMENT, `c1` int(11) DEFAULT NULL, `c2` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `c1` (`c1`) ) ENGINE=InnoDBAUTO_INCREMENT=6DEFAULT CHARSET=utf8
|
3 |
查看表数据 |
|
id | c1 | c2 ---+------+------ 1 | 1 | abc 2 | 2 | abc 3 | 3 | abcdd 4 | 4 | abcdd 5 | 5 | abcdd |
id | c1 | c2 ---+------+------ 1 | 1 | abc 2 | 2 | abc 3 | 3 | abcdd 4 | 4 | abcdd 5 | 5 | abcdd |
4 |
查看binlog位置 show master status\G |
记录当前binlog位点, 后续可以查看replace动作产生的binlog事件 |
mysql-bin.000038 59242888 |
|
5 |
replace操作 replace into test_autoinc(c1,c2) values(2,'eeee'); |
影响两条记录,主库replace= delete+insert |
Query OK, 2 rows affected (0.00 sec) |
|
6 |
查看表数据 |
|
id | c1 | c2 ---+------+------- 1 | 1 | abc 3 | 3 | abcdd 4 | 4 | abcdd 5 | 5 | abcdd 6 | 2 | eeee |
id | c1 | c2 ---+------+------- 1 | 1 | abc 3 | 3 | abcdd 4 | 4 | abcdd 5 | 5 | abcdd 6 | 2 | eeee |
7 |
查看binlog事件 show binlog events in 'mysql-bin.000038' from 59242888; |
也可以通过mysqlbinlog工具分析日志,查询从库执行的update语句 |
Pos | Event_type ---------+--------------- 59242888 | Query 59242957 | Table_map 59243013 |Update_rows_v1 59243072 | Xid |
|
8 |
查看自增列值 Show create table |
此时master的自增列为7,而slave的自增列为6,与表内最大值相同 |
CREATE TABLE `test_autoinc` ( `id` int(11) NOT NULL AUTO_INCREMENT, `c1` int(11) DEFAULT NULL, `c2` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `c1` (`c1`) ) ENGINE=InnoDBAUTO_INCREMENT=7 |
CREATE TABLE `test_autoinc` ( `id` int(11) NOT NULL AUTO_INCREMENT, `c1` int(11) DEFAULT NULL, `c2` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `c1` (`c1`) ) ENGINE=InnoDBAUTO_INCREMENT=6 |
经过第8步操作后,若发生主备切换,slave提供服务,此时通过自增列插入主键6的记录,就会发生主键冲突。
如何解决这个bug?对于replace操作,生成binlog时也生成delete和insert两个事件而非一个update事件;或者在执行update更新主键的同时也更新自增列值。当然了,这个只是纯原理分析,具体采用什么方法解这个问题,要根据mysql内部的实现,避免引入新的问题。这个bug我同事已经提交到社区,http://bugs.mysql.com/73563 ,大家可以看看。

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

大数据结构处理技巧:分块:分解数据集并分块处理,减少内存消耗。生成器:逐个产生数据项,无需加载整个数据集,适用于无限数据集。流:逐行读取文件或查询结果,适用于大文件或远程数据。外部存储:对于超大数据集,将数据存储在数据库或NoSQL中。

可以通过以下方式优化MySQL查询性能:建立索引,将查找时间从线性复杂度降至对数复杂度。使用PreparedStatements,防止SQL注入并提高查询性能。限制查询结果,减少服务器处理的数据量。优化连接查询,包括使用适当的连接类型、创建索引和考虑使用子查询。分析查询,识别瓶颈;使用缓存,减少数据库负载;优化PHP代码,尽量减少开销。

在PHP中备份和还原MySQL数据库可通过以下步骤实现:备份数据库:使用mysqldump命令转储数据库为SQL文件。还原数据库:使用mysql命令从SQL文件还原数据库。

如何将数据插入MySQL表中?连接到数据库:使用mysqli建立与数据库的连接。准备SQL查询:编写一个INSERT语句以指定要插入的列和值。执行查询:使用query()方法执行插入查询,如果成功,将输出一条确认消息。

要在PHP中使用MySQL存储过程:使用PDO或MySQLi扩展连接到MySQL数据库。准备调用存储过程的语句。执行存储过程。处理结果集(如果存储过程返回结果)。关闭数据库连接。

使用PHP创建MySQL表需要以下步骤:连接到数据库。创建数据库(如果不存在)。选择数据库。创建表。执行查询。关闭连接。

MySQL 8.4(截至 2024 年的最新 LTS 版本)中引入的主要变化之一是默认情况下不再启用“MySQL 本机密码”插件。此外,MySQL 9.0完全删除了这个插件。 此更改会影响 PHP 和其他应用程序

Oracle数据库和MySQL都是基于关系模型的数据库,但Oracle在兼容性、可扩展性、数据类型和安全性方面更胜一筹;而MySQL则侧重速度和灵活性,更适合小到中等规模的数据集。①Oracle提供广泛的数据类型,②提供高级安全功能,③适合企业级应用程序;①MySQL支持NoSQL数据类型,②安全性措施较少,③适合小型到中等规模应用程序。
