事务概念
A transaction symbolizes a unit of work performed within a database management system (or similar system) against a database, and treated in a coherent and reliable way independent of other transactions. A transaction generally represents any change in a database.
wiki上是这样定义事务的: 事务表示在数据库管理系统中对数据库执行的单元操作,并且以连贯和可靠的方式独立于其他事务。通常数据库中的所有修改都是事务。
数据库设置事务的目标:
1. 提供可靠的单元操作,以让数据库从错误中恢复过来,并且保持数据库的一致性即使系统错误;
2. 提供多个应用同时访问数据库时的隔离功能。
PS,通常所有单条操作语句都是事务,在mysql中查看提交方式:SELECT @@autocommit,为1表示自动提交,为0表示手动提交
事务特性(ACID)
根据定义,可以得到数据库的特性。
1. 原子性(Atomicity):事务是一个单元操作,具有原子性,要么全部执行,要么全部不执行;
2. 一致性(Consistency):事务执行后要保证数据库状态的一致性,所以写入的数据都要经过规则(约束、级联和触发器等)校验;
3. 隔离性(Isolation):事务并发执行时,是彼此隔离的,数据库有多个隔离级别,不同的级别有不同的效果;
4. 持久性(Durability):事务完成后,修改应当永久保存在数据库中,即使系统挂了。
事务隔离级别
事务在并发执行时,会带来数据上的冲突,为了解决这些冲突,数据库使用加锁或者其他方式来处理,但是大家都知道并发控制会降低执行效率,数据库一般都会提供数个隔离级别,让用户在并发效率和并发控制之间进行权衡和选择。 ANSI/ISO SQL标准定义了四个隔离级别:
序列化(Serializable)
这是最高级的隔离级别,数据实现彻底同步。
可重复读(Repeatable read)
在一个事务中,保证两次读取的数据是相同的。
可能会会出现幻读(Phantom reads),简单来说就是两次查询多条数据,后面那次查询可能会出来多出来N条数据或者少了N条数据。
来看个粟子,假如table_x中有id为’1’和’2’的两条数据
事务1 | 事务2 |
---|---|
select * from table_x where id between ‘1’ and ‘5’; | |
insert into table_x values(‘4’,’0’); | |
commit | |
select * from table_x where id between ‘1’ and ‘5’; |
事务1第一次查询时,返回’1’,’2’两条数据,第二次查询时返回了’1’,’2’,’4’三条数据。
读已提交(Read committed)
在一个事务中,保证读取的数据是已提交的。
可能会出现幻读。
可能会出现不可重复读(Non-repeatable reads)
不可重复读,简单来说就是两次查询,结果集不一样,后面那次查询的结果,数据可能发生了变化,举个粟子:
事务1 | 事务2 |
---|---|
select value from table_x where id=’1’; #get value=5 | |
update table_x set value=’10’ where id=’1’; | |
commit | |
select value from table_x where id=’1’; #get value=10 |
读未提交(Read uncommitted)
这是最低的隔离级别,A事务可以读取B事务已修改未提交的数据。
可能会出现幻读。
可能会出现不可重复读。
可能会出现脏读(Dirty reads)
脏读,即读取到未提交的事务的修改,如果事务提交失败,意味着读取的数据是错的(就算不失败,也有可能导致其它问题),来个栗子:事务1和2并发执行,执行前表table_x中id=1的数据value=3,现在执行事务:
事务1 | 事务2 |
---|---|
update table_x set value=’5’ where id=’1’; | |
select value from table_x where id=’1’; #value=5 | |
some thing error , roll back |
那么这样事务1得到的值是错的,数据库中不存在(id=1,value=5)这条记录。
事务隔离级别&读问题
Isolation level | Dirty reads | Non-repeatable reads | Phantoms |
---|---|---|---|
Read Uncommitted | may occur | may occur | may occur |
Read Committed | don’t occur | may occur | may occur |
Repeatable Read | don’t occur | don’t occur | may occur |
Serializable | don’t occur | don’t occur | don’t occur |
事务的默认隔离级别
mysql – repeatable read (使用select @@tx_isolation 查看当前隔离级别)
oracle – read commited
案例分析
隔离级别: repeatable read
场景: 订单发货后通知用户
//in service A
@Transactional
public void deliver(String orderId){
Order order=orderService.findById(orderId);
...
order.setStatus("delivered");
notifyOrderChange(orderId);
...
}
//in service B
@Transactional
@Async
public void notifyOrderChange(String orderId){
Order order=orderService.findById(orderId);
pushOrderInfoToUser(order);
}
发现用户收到的信息,有时候订单的状态仍然是未发货状态。
根据上面理论知识很容易会发现,由于@Async导致异步调用,deliver和notifyOrderChange运行在不同的事务中,在并发执行时,由于repeatable read的隔离级别,只能读取已提交的数据,所以当notifyOrderChange先于deliver的提交执行时,notifyOrderChange得不到order的修改,这样用户看到的订单仍然是未发货状态了。解决办法就在deliver提交成功后再执行notifyOrderChange.
参考资料
https://en.wikipedia.org/wiki/Database_transaction
https://en.wikipedia.org/wiki/Isolation_(database_systems)
https://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html