最近的項(xiàng)目中遇到一項(xiàng)問(wèn)題,并發(fā)更新某一單據(jù)的時(shí)候,出現(xiàn)了更新失效的情況。比如:
10年積累的網(wǎng)站設(shè)計(jì)、網(wǎng)站制作經(jīng)驗(yàn),可以快速應(yīng)對(duì)客戶對(duì)網(wǎng)站的新想法和需求。提供各種問(wèn)題對(duì)應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識(shí)你,你也不認(rèn)識(shí)我。但先網(wǎng)站設(shè)計(jì)后付款的網(wǎng)站建設(shè)流程,更有馬鞍山免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。
@Transactional(rollbackFor = Exception.class)
public void update(Integer id){
//1.按id查詢
//2.更新某一字段的值
}
生成的SQL大概是這樣的:
UPDATE table
SET field = #{field,jdbcType=INTEGER}
WHERE id= 1
那么以上代碼產(chǎn)生的問(wèn)題就是:
對(duì)于同一個(gè)id=1來(lái)說(shuō),請(qǐng)求A與請(qǐng)求B都進(jìn)到了update方法中,此時(shí)按id查詢得到的信息是相同的,那么請(qǐng)求A更新了id=1的這條記錄之后,此時(shí),請(qǐng)求B又對(duì)id=1的記錄進(jìn)行更新,此時(shí)注意請(qǐng)求B更新id=1的記錄的時(shí)候,請(qǐng)求B按id查詢到的數(shù)據(jù)已經(jīng)是舊數(shù)據(jù)了,請(qǐng)求B執(zhí)行完之后,請(qǐng)求A的更新被請(qǐng)求B覆蓋了。
如果更新的是狀態(tài),那倒無(wú)所謂,如果庫(kù)存量呢?
如果更新的是庫(kù)存,是在這條記錄的原始值上進(jìn)行+或者-操作,是不是就出問(wèn)題了?
事后寫(xiě)了個(gè)小demo來(lái)復(fù)現(xiàn)問(wèn)題,代碼大致如下:
需求: 某個(gè)分類每被訪問(wèn)一次,排序就+1;
開(kāi)啟兩個(gè)客戶端同時(shí)訪問(wèn)
@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值數(shù)據(jù)庫(kù)初始為:0
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
大致意思:
請(qǐng)求A與請(qǐng)求B,同時(shí)操作id=6的這條記錄時(shí),查詢出來(lái)id=6的這條記錄,然后請(qǐng)求A對(duì)其進(jìn)行了修改 SET sort=1操作,完事之后,請(qǐng)求A的事務(wù)提交了。而此時(shí)請(qǐng)求B,通過(guò)id=6條件查詢到的category的信息是請(qǐng)求A提交之前的,此時(shí)請(qǐng)求B執(zhí)行了SET sort=1,覆蓋了請(qǐng)求B的修改操作。
解決方法其它很簡(jiǎn)單,我們上面可以看到,update語(yǔ)句是直接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}
我們執(zhí)行試一下,看下日志:
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
看到了吧,保證數(shù)據(jù)一致了吧
我之前的文章有對(duì)MySQL事務(wù)這塊做過(guò)詳解,想知道真正原理的小伙伴請(qǐng)閱讀: 深入理解mysql事務(wù)
注意,我這里使用的數(shù)據(jù)庫(kù)是mysql8以上,應(yīng)用服務(wù)器是單機(jī)版的。