Mysql并发保证数据一致性——实例
发表于:2025-11-06 作者:千家信息网编辑
千家信息网最后更新 2025年11月06日,1. 背景最近的项目中遇到一项问题,并发更新某一单据的时候,出现了更新失效的情况。比如:@Transactional(rollbackFor = Exception.class)public void
千家信息网最后更新 2025年11月06日Mysql并发保证数据一致性——实例
1. 背景
最近的项目中遇到一项问题,并发更新某一单据的时候,出现了更新失效的情况。比如:
@Transactional(rollbackFor = Exception.class)public void update(Integer id){ //1.按id查询 //2.更新某一字段的值}生成的SQL大概是这样的:
UPDATE table SET field = #{field,jdbcType=INTEGER}WHERE id= 1那么以上代码产生的问题就是:
对于同一个id=1来说,请求A与请求B都进到了update方法中,此时按id查询得到的信息是相同的,那么请求A更新了id=1的这条记录之后,此时,请求B又对id=1的记录进行更新,此时注意请求B更新id=1的记录的时候,请求B按id查询到的数据已经是旧数据了,请求B执行完之后,请求A的更新被请求B覆盖了。
如果更新的是状态,那倒无所谓,如果库存量呢?
如果更新的是库存,是在这条记录的原始值上进行+或者-操作,是不是就出问题了?
2. 分析产生的原因
事后写了个小demo来复现问题,代码大致如下:
需求: 某个分类每被访问一次,排序就+1;
开启两个客户端同时访问
@Transactional(rollbackFor = Exception.class)public void updateStatusTest(Integer id) { System.out.println("start:"+Thread.currentThread().getName()); Category category = categoryMapper.selectById(id); try { Thread.sleep(8000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":"+category); category.setSort(category.getSort()+1); categoryMapper.updateById(category); category = categoryMapper.selectById(id); System.out.println(Thread.currentThread().getName()+":"+category); System.out.println("end:"+Thread.currentThread().getName());}DAO层使用的mybatis,生成日志大致如下:
需要更新的sort值数据库初始为:0
start:http-nio-8062-exec-1Creating a new SqlSessionRegistering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6df7e3e9]JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@2d5c951c] will be managed by Spring==> Preparing: SELECT cid,category_name,parent_id,image_url,icon_url,sort,status FROM t_admin_category WHERE cid=? ==> Parameters: 6(Integer)<== Columns: cid, category_name, parent_id, image_url, icon_url, sort, status<== Row: 6, 1, 0, 11111, 1, 0, 1111<== Total: 1Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6df7e3e9] Time:27 ms - ID:com.sxl.simple.shop.admin.category.mapper.CategoryMapper.selectByIdExecute SQL: SELECT cid, category_name, parent_id, image_url, icon_url, sort, status FROM t_admin_category WHERE cid=6start:http-nio-8062-exec-2Creating a new SqlSessionRegistering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7af43046]JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@700c5b36] will be managed by Spring==> Preparing: SELECT cid,category_name,parent_id,image_url,icon_url,sort,status FROM t_admin_category WHERE cid=? Time:2 ms - ID:com.sxl.simple.shop.admin.category.mapper.CategoryMapper.selectByIdExecute SQL: SELECT cid, category_name, parent_id, image_url, icon_url, sort, status FROM t_admin_category WHERE cid=6==> Parameters: 6(Integer)<== Columns: cid, category_name, parent_id, image_url, icon_url, sort, status<== Row: 6, 1, 0, 11111, 1, 0, 1111<== Total: 1Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7af43046]http-nio-8062-exec-1:Category{cid=6, categoryName='1', parentId=0, imageUrl='11111', iconUrl='1', sort=0, status='1111'}Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6df7e3e9] from current transaction==> Preparing: UPDATE t_admin_category SET category_name=?, parent_id=?, image_url=?, icon_url=?, sort=?, status=? WHERE cid=? ==> Parameters: 1(String), 0(Integer), 11111(String), 1(String), 1(Integer), 1111(String), 6(Integer) Time:16 ms - ID:com.sxl.simple.shop.admin.category.mapper.CategoryMapper.updateByIdExecute SQL: UPDATE t_admin_category SET category_name='1', parent_id=0, image_url='11111', icon_url='1', sort=1, status='1111' WHERE<== Updates: 1 cid=6Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6df7e3e9] Time:0 ms - ID:com.sxl.simple.shop.admin.category.mapper.CategoryMapper.selectByIdExecute SQL:Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6df7e3e9] from current transaction SELECT cid, category_name,==> Preparing: SELECT cid,category_name,parent_id,image_url,icon_url,sort,status FROM t_admin_category WHERE cid=? parent_id,==> Parameters: 6(Integer) image_url, icon_url,<== Columns: cid, category_name, parent_id, image_url, icon_url, sort, status sort, status <== Row: 6, 1, 0, 11111, 1, 1, 1111 FROM<== Total: 1 t_admin_category WHEREReleasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6df7e3e9] cid=6http-nio-8062-exec-1:Category{cid=6, categoryName='1', parentId=0, imageUrl='11111', iconUrl='1', sort=1, status='1111'}end:http-nio-8062-exec-1Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6df7e3e9]Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6df7e3e9]Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6df7e3e9]http-nio-8062-exec-2:Category{cid=6, categoryName='1', parentId=0, imageUrl='11111', iconUrl='1', sort=0, status='1111'}Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7af43046] from current transaction==> Preparing: UPDATE t_admin_category SET category_name=?, parent_id=?, image_url=?, icon_url=?, sort=?, status=? WHERE cid=? Time:0 ms - ID:com.sxl.simple.shop.admin.category.mapper.CategoryMapper.updateByIdExecute SQL: UPDATE t_admin_category SET category_name='1', parent_id=0, image_url='11111', icon_url='1', sort=1, status='1111' WHERE cid=6==> Parameters: 1(String), 0(Integer), 11111(String), 1(String), 1(Integer), 1111(String), 6(Integer)<== Updates: 1Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7af43046]Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7af43046] from current transaction==> Preparing: SELECT cid,category_name,parent_id,image_url,icon_url,sort,status FROM t_admin_category WHERE cid=? ==> Parameters: 6(Integer)<== Columns: cid, category_name, parent_id, image_url, icon_url, sort, status<== Row: 6, 1, 0, 11111, 1, 0, 1111 Time:16 ms - ID:com.sxl.simple.shop.admin.category.mapper.CategoryMapper.selectByIdExecute SQL: SELECT cid, category_name,<== Total: 1 parent_id, image_url,Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7af43046] icon_url, sort, status FROMhttp-nio-8062-exec-2:Category{cid=6, categoryName='1', parentId=0, imageUrl='11111', iconUrl='1', sort=0, status='1111'} t_admin_category end:http-nio-8062-exec-2 WHERE cid=6大致意思:
请求A与请求B,同时操作id=6的这条记录时,查询出来id=6的这条记录,然后请求A对其进行了修改 SET sort=1操作,完事之后,请求A的事务提交了。而此时请求B,通过id=6条件查询到的category的信息是请求A提交之前的,此时请求B执行了SET sort=1,覆盖了请求B的修改操作。
3. 解决思路
解决方法其它很简单,我们上面可以看到,update语句是直接set sort=1的,我们只需要改为set sort = sort +1 就可以了。你不信?那我改一下试试:
@Transactional(rollbackFor = Exception.class)public void updateStatusTest(Integer id) { System.out.println("start:"+Thread.currentThread().getName()); Category category = categoryMapper.selectById(id); try { Thread.sleep(8000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":"+category);// category.setSort(category.getSort()+1); categoryMapper.updateStatusById(category); category = categoryMapper.selectById(id); System.out.println(Thread.currentThread().getName()+":"+category); System.out.println("end:"+Thread.currentThread().getName());}Mapper文件:
UPDATE t_admin_category SET sort =sort + 1 WHERE cid= #{cid,jdbcType=INTEGER} 我们执行试一下,看下日志:
start:http-nio-8062-exec-1Creating a new SqlSessionRegistering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e925f3f]JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@6c17839b] will be managed by Spring==> Preparing: SELECT cid,category_name,parent_id,image_url,icon_url,sort,status FROM t_admin_category WHERE cid=? ==> Parameters: 6(Integer)<== Columns: cid, category_name, parent_id, image_url, icon_url, sort, status<== Row: 6, 1, 0, 11111, 1, 0, 1111<== Total: 1Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e925f3f] Time:43 ms - ID:com.sxl.simple.shop.admin.category.mapper.CategoryMapper.selectByIdExecute SQL: SELECT cid, category_name, parent_id, image_url, icon_url, sort, status FROM t_admin_category WHERE cid=6start:http-nio-8062-exec-2Creating a new SqlSessionRegistering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c88f022]JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@195f8b5a] will be managed by Spring==> Preparing: SELECT cid,category_name,parent_id,image_url,icon_url,sort,status FROM t_admin_category WHERE cid=? ==> Parameters: 6(Integer)<== Columns: cid, category_name, parent_id, image_url, icon_url, sort, status<== Row: 6, 1, 0, 11111, 1, 0, 1111<== Total: 1Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c88f022] Time:2 ms - ID:com.sxl.simple.shop.admin.category.mapper.CategoryMapper.selectByIdExecute SQL: SELECT cid, category_name, parent_id, image_url, icon_url, sort, status FROM t_admin_category WHERE cid=6http-nio-8062-exec-1:Category{cid=6, categoryName='1', parentId=0, imageUrl='11111', iconUrl='1', sort=0, status='1111'}Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e925f3f] from current transaction==> Preparing: UPDATE t_admin_category SET sort =sort + 1 WHERE cid= ? ==> Parameters: 6(Integer)<== Updates: 1Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e925f3f]Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e925f3f] from current transaction==> Preparing: SELECT cid,category_name,parent_id,image_url,icon_url,sort,status FROM t_admin_category WHERE cid=? Time:0 ms - ID:com.sxl.simple.shop.admin.category.mapper.CategoryMapper.updateStatusByIdExecute SQL: UPDATE t_admin_category SET sort =sort + 1 WHERE cid= 6==> Parameters: 6(Integer) Time:0 ms - ID:com.sxl.simple.shop.admin.category.mapper.CategoryMapper.selectById<== Columns: cid, category_name, parent_id, image_url, icon_url, sort, statusExecute SQL:<== Row: 6, 1, 0, 11111, 1, 1, 1111 SELECT<== Total: 1 cid,Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e925f3f] category_name, parent_id,http-nio-8062-exec-1:Category{cid=6, categoryName='1', parentId=0, imageUrl='11111', iconUrl='1', sort=1, status='1111'} image_url,end:http-nio-8062-exec-1 icon_url, sort,Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e925f3f] status FROMTransaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e925f3f] t_admin_category WHERETransaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e925f3f] cid=6 Time:0 ms - ID:com.sxl.simple.shop.admin.category.mapper.CategoryMapper.updateStatusByIdExecute SQL: UPDATE t_admin_category SET sort =sort + 1 WHERE cid= 6 Time:0 ms - ID:com.sxl.simple.shop.admin.category.mapper.CategoryMapper.selectByIdExecute SQL: SELECT cid, category_name, parent_id, image_url, icon_url, sort, status FROM t_admin_category WHERE cid=6http-nio-8062-exec-2:Category{cid=6, categoryName='1', parentId=0, imageUrl='11111', iconUrl='1', sort=0, status='1111'}Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c88f022] from current transaction==> Preparing: UPDATE t_admin_category SET sort =sort + 1 WHERE cid= ? ==> Parameters: 6(Integer)<== Updates: 1Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c88f022]Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c88f022] from current transaction==> Preparing: SELECT cid,category_name,parent_id,image_url,icon_url,sort,status FROM t_admin_category WHERE cid=? ==> Parameters: 6(Integer)<== Columns: cid, category_name, parent_id, image_url, icon_url, sort, status<== Row: 6, 1, 0, 11111, 1, 2, 1111<== Total: 1Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c88f022]http-nio-8062-exec-2:Category{cid=6, categoryName='1', parentId=0, imageUrl='11111', iconUrl='1', sort=2, status='1111'}end:http-nio-8062-exec-2看到了吧,保证数据一致了吧
我之前的文章有对Mysql事务这块做过详解,想知道真正原理的小伙伴请阅读: 深入理解mysql事务
注意,我这里使用的数据库是mysql8以上,应用服务器是单机版的。
更新
数据
查询
问题
事务
代码
信息
同时
库存
数据库
方法
日志
时候
生成
一致
保证
原始
相同
无所谓
两个
数据库的安全要保护哪些东西
数据库安全各自的含义是什么
生产安全数据库录入
数据库的安全性及管理
数据库安全策略包含哪些
海淀数据库安全审计系统
建立农村房屋安全信息数据库
易用的数据库客户端支持安全管理
连接数据库失败ssl安全错误
数据库的锁怎样保障安全
安徽惠普服务器虚拟化费用
易语言 数据库安全
谷安网络安全培训机构
网络安全保障方案人员培训
零基础学软件开发有用吗
简易版数据库有什么不同
dell服务器后面的灯i
数据库应用技术期末答案
家庭教育与网络安全专题讲座
重庆惠普服务器虚拟化操作服务器
网络游戏 大数据库
英雄联盟手游那个服务器不卡
国家青少年网络安全基地
河南前端软件开发哪家可靠
数据库营销惠普
乡镇网络安全检查自查报告
浙江常规网络技术咨询价格行情
k3服务器中间层配置
中科软磐云网络安全设备
哪里可以导出数据库
广州星伦网络技术有限公司
违反网络安全法47条处罚
服务器意外断电后数据一定会丢失
天津浪潮服务器虚拟化设计云主机
计算机网络安全章节选择题
服务器3位管理员
数据库和电脑关系
阿里云发件服务器是什么
华为服务器内存条怎么拆
《网络安全法》发布时间