多PC访问同一服务器数据库时,代码取Max值自增导致重复记录问题
解决多客户端插入时的重复主键问题
这个问题太典型了——你现在的逻辑是先查最大值再手动自增,单客户端跑完全没问题,但多PC并发操作时,就会触发「竞态条件」:两个客户端同时读到相同的max值,各自自增后插入,自然就产生重复记录了。
下面给你几个靠谱的解决方案,按推荐优先级排序:
1. 让数据库自动生成主键(最推荐)
这是最简单也最可靠的方案,把主键字段设置成数据库的自增字段(不同数据库语法略有差异):
- MySQL/MariaDB:定义字段时用
INT PRIMARY KEY AUTO_INCREMENT - SQL Server:用
INT PRIMARY KEY IDENTITY(1,1) - PostgreSQL:用
SERIAL或IDENTITY类型
之后插入数据时,完全不用管主键的值,数据库会自动帮你生成唯一的递增主键,彻底规避并发冲突。比如插入语句可以写成:
INSERT INTO your_table (field1, field2) VALUES (?, ?);
数据库会全权处理主键生成,根本不需要提前查max值。
2. 使用事务+锁表(适合无法修改主键类型的场景)
如果因为历史限制不能改用自增主键,那可以用事务+锁表的方式,确保同一时间只有一个客户端能执行「查max+插入」的操作:
BEGIN TRANSACTION; -- 锁定整张表,阻止其他客户端的读写操作 LOCK TABLE your_table WRITE; -- 获取当前主键最大值 SELECT MAX(primary_key) FROM your_table INTO @max_id; -- 自增后执行插入 INSERT INTO your_table (primary_key, field1, field2) VALUES (@max_id + 1, ?, ?); COMMIT;
不过这种方式会降低并发性能,锁表期间其他客户端的插入请求会被阻塞,所以只适合并发量不高的场景。
3. 使用乐观锁(适合高并发但冲突率低的场景)
乐观锁的思路是不主动加锁,而是在插入时校验当前最大值是否和之前查到的一致,一致就插入,不一致就重试:
- 先查询当前主键最大值:
SELECT MAX(primary_key) FROM your_table,假设得到current_max - 插入时带条件校验:
INSERT INTO your_table (primary_key, field1, field2) SELECT current_max + 1, ?, ? WHERE (SELECT MAX(primary_key) FROM your_table) = current_max;
- 检查插入是否成功(比如看SQL执行后的影响行数),如果影响行数为0,说明已有其他客户端插入了新数据,此时重试整个流程(重新查max再插入)
这种方式不会阻塞其他操作,并发性能好,但冲突率高的话会有较多重试,需要在代码里处理重试逻辑。
补充:你的原逻辑出问题的核心原因
举个直观的例子:
客户端A执行
SELECT MAX(id)得到10,准备插入11
同一时间客户端B也执行SELECT MAX(id),此时A还没完成插入,所以B也拿到10
随后A插入11,B也插入11,重复主键就产生了
这就是典型的非原子操作漏洞——「查max」和「插入」是两个独立步骤,中间的时间间隙被其他客户端钻了空子。
内容的提问来源于stack exchange,提问作者Adham Hatem
相关产品推荐
相关产品推荐

