1:数据库客户端操作未提交导致行锁定
在进行更新时,很多时候我们喜欢使用for update,对行进行锁定后,修改相关值,然后commit。
在操作过程中,有时候会忘记commit或者commit滞后时间比较长,此时会给系统带来一些潜在的风险。
ORACLE在对行进行for update操作时,在行上加入独占锁,这是其它会话(包含应用端)恰好也更新此行,会导致该会话堵塞等待。
尤其是对多行数据进行for update锁定,长时间不提交或者忘记提交,会导致应用端更新出现不可预料的问题。
切记在业务空闲时,进行这些操作,且及时commit。
2:批量更新导致的死锁
在编写程序时,尤其是高并发情况下,不正确的批量更新将导致数据库的死锁。
public void deductStock(int[] productIds) throws Exception {
if(productIds != null && productIds.length > 0 ) {
for(int pId : productIds) {
// product stock deduct 1(default)
updateStock(pId);
}
}
}
如果按照购物车的加入顺序传递商品ID列表,可能会导致A购物车[1,2,3],B购物车[2,1,3],那么在提交扣减库存时,可能会发生
A操作会话锁定1行时,B操作会话锁定2行时,此时A会话要获取1行的独占锁,B会话要获取2行的独占锁,相互等待,导致死锁。
此情况一般不会出现,如果有多个热点商品(商品做活动,优惠力度比较大),而代码恰好有此bug,就有很大的几率引发死锁。
3:不正确的程序调用导致事务问题
目前spring框架,通过AOP配置,一般我们调用服务层业务方法时,就是事务的开始点,业务方法结束就是事务的完结点。
本来应该在一个事务的操作,由于对事务的不了解,导致写出事务不一致的代码,例如:
public void action() {
try {
//更新书籍类目
bookCategorySevice.update(bookCategory);
//更新图书信息
bookSevice.update(book);
}catch(Exception e) {
logger.error(" operator is error!!!",e);
}
}
从业务上,更新书籍类目和更新图书信息应该在一个操作完成,单从控制层代码看也没有问题,但是如果考虑到事务的切入点,这就是2个事务,会出现不一致问题。
同来,有时候就是应该是两个事务操作,如果写在一个事务也是操作,例如审计记录,更新图书信息动作不管成功与否都应该有一条审计记录。代码写成:
public void action() {
try {
//更新图书信息
bookSevice.update(book);
}catch(Exception e) {
logger.error(" operator is error!!!",e);
}
}
审计记录动作在update业务操作中,这样就不能满足业务要求,会导致审计记录的缺失。
4:事务回滚陷阱
在业务方法中做一些非事务的操作时,例如文件操作,如果和事务没有关联,建议写入单独的业务方法,在控制层单独调用。
如果不方便写入单独方法,可以把文件操作的异常进行单独捕获处理,不让异常影响事务操作。
一般事务的回滚都是以捕获异常为回滚点,进行事务回滚,如果是和事务操作无关的异常抛出,导致异常回滚,就会业务的正确性。
public void update() throws Exception {
//更新图书信息
bookDao.update(book);
//写入文件内容,此操作和事务无关,但是抛出异常,事务照样回滚
fileUtil.write(info);
}
当然也可以指定特殊的异常对象类似为回滚,最后还是对此操作进行单独的异常处理,不抛出异常。
5:异步操作事务陷阱
如果我们在业务方法操作中,创建异步线程进行一些操作,又传递服务层对象到异步线程中,在异步线程的操作中调用传递的服务层对象方法,
此时会出现事务异常,因为事务实例是线程共享变量,而新创建的异步线程中,还没有此共享变量,所以报错。需要在异步线程重新进行事务的切入点调用,才能正常使
用,暂不写代码实例了。
6:事务并发提交陷阱
应用端有时候会出现同时更新同一条记录,两条都更新成功,单结果只能是其中一条的更新结果,一般我们会引入版本号来解决。