最近的項目中遇到一項問題,併發更新某一單據的時候,出現了更新失效的狀況。好比:mysql
@Transactional(rollbackFor = Exception.class) public void update(Integer id){ //1.按id查詢 //2.更新某一字段的值 }
生成的SQL大概是這樣的: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覆蓋了。數據庫
若是更新的是狀態,那倒無所謂,若是庫存量呢?
若是更新的是庫存,是在這條記錄的原始值上進行+或者-操做,是否是就出問題了?apache
過後寫了個小demo來複現問題,代碼大體以下:服務器
需求: 某個分類每被訪問一次,排序就+1;session
@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值數據庫初始爲:0mybatis
start:http-nio-8062-exec-1 Creating a new SqlSession Registering 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: 1 Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6df7e3e9] Time:27 ms - ID:com.sxl.simple.shop.admin.category.mapper.CategoryMapper.selectById Execute SQL: SELECT cid, category_name, parent_id, image_url, icon_url, sort, status FROM t_admin_category WHERE cid=6 start:http-nio-8062-exec-2 Creating a new SqlSession Registering 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.selectById Execute 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: 1 Releasing 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.updateById Execute 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=6 Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6df7e3e9] Time:0 ms - ID:com.sxl.simple.shop.admin.category.mapper.CategoryMapper.selectById Execute 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 WHERE Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6df7e3e9] cid=6 http-nio-8062-exec-1:Category{cid=6, categoryName='1', parentId=0, imageUrl='11111', iconUrl='1', sort=1, status='1111'} end:http-nio-8062-exec-1 Transaction 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.updateById Execute 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: 1 Releasing 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.selectById Execute 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 FROM http-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的修改操做。併發
解決方法其它很簡單,咱們上面能夠看到,update語句是直接set sort=1的,咱們只須要改成set sort = sort +1 就能夠了。你不信?那我改一下試試:app
@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文件:ide
<update id="updateStatusById" parameterType="com.sxl.simple.shop.admin.category.entity.Category"> UPDATE t_admin_category SET sort =sort + 1 WHERE cid= #{cid,jdbcType=INTEGER} </update>
咱們執行試一下,看下日誌:
start:http-nio-8062-exec-1 Creating a new SqlSession Registering 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: 1 Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e925f3f] Time:43 ms - ID:com.sxl.simple.shop.admin.category.mapper.CategoryMapper.selectById Execute SQL: SELECT cid, category_name, parent_id, image_url, icon_url, sort, status FROM t_admin_category WHERE cid=6 start:http-nio-8062-exec-2 Creating a new SqlSession Registering 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: 1 Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c88f022] Time:2 ms - ID:com.sxl.simple.shop.admin.category.mapper.CategoryMapper.selectById Execute SQL: SELECT cid, category_name, parent_id, image_url, icon_url, sort, status FROM t_admin_category WHERE cid=6 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@5e925f3f] from current transaction ==> Preparing: UPDATE t_admin_category SET sort =sort + 1 WHERE cid= ? ==> Parameters: 6(Integer) <== Updates: 1 Releasing 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.updateStatusById Execute 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, status Execute 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 FROM Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e925f3f] t_admin_category WHERE Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e925f3f] cid=6 Time:0 ms - ID:com.sxl.simple.shop.admin.category.mapper.CategoryMapper.updateStatusById Execute SQL: UPDATE t_admin_category SET sort =sort + 1 WHERE cid= 6 Time:0 ms - ID:com.sxl.simple.shop.admin.category.mapper.CategoryMapper.selectById Execute SQL: SELECT cid, category_name, parent_id, image_url, icon_url, sort, status FROM t_admin_category WHERE cid=6 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@6c88f022] from current transaction ==> Preparing: UPDATE t_admin_category SET sort =sort + 1 WHERE cid= ? ==> Parameters: 6(Integer) <== Updates: 1 Releasing 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: 1 Releasing 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以上,應用服務器是單機版的。