数据库事务ACID四个特性
原子性(Atomic)
- 事务中的多个操作,不可分割,要么都成功,要么都失败; All or Nothing.
一致性(Consistency)
- 事务操作之后, 数据库所处的状态和业务规则是一致的; 比如a,b账户相互转账之后,总金额不变;
隔离性(Isolation)
- 多个事务之间就像是串行执行一样,不相互影响;
持久性(Durability)
- 事务提交后被持久化到永久存储。
脏读(Dirty Read)
- 脏读意味着一个事务读取了另一个事务未提交的数据,而这个数据是有可能回滚的
- 例如:app中的充值,充值事务进行的时候,日志事务报错回滚,钱已经回退了,但是代币还是看到了。
不可重复读(Unrepeatable Read)
- 不可重复读意味着,在数据库访问中,一个事务范围内不同位置的两个相同的查询却返回了不同数据。
- 例如:事务B中对某个查询执行两次,当第一次执行完时,事务A对其数据进行了修改。事务B中第二次查询数据发生了改变
- 因为其他事务update了相关记录
- 如果要防止不可重复读,只需要把相关数据锁住即可,别人就无法update了
幻读(phantom read)
- 同一个事务中多次执行同一个select, 读取到的数据行发生改变。也就是行数减少或者增加了(被其它事务delete/insert并且提交)。
- 例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。
- 如果防止幻读,要锁住满足条件的记录及所有这些记录之间的gap,也就是需要 gap lock,比不可重复读要复杂。
隔离机制
READ UNCOMMITTED
- 可以读取未提交的数据
- RU 模式允许脏读、幻读、不可重复读
READ COMMITTED
- 只能读取已经提交的数据
- RC 允许幻读和不可重复读,不允许脏读
REPEATABLE READ
- 同一个事务中多次执行同一个select,读取到的数据没有发生改变;
- RR 允许幻读,不允许不可重复读、脏读
SERIALIZABLE:
- 均不允许
数据库对应隔离级别
- Oracle 实现了RC 和 SERIALIZABLE隔离级别
- 默认采用RC隔离级别,不允许脏读,允许不可重复读和幻读。
- SERIALIZABLE 均不允许
- mysql
- 默认采用RR隔离级别,SQL标准是要求RR解决不可重复读的问题,但是因为MySQL采用了gap lock,所以实际上MySQL的RR隔离级别也解决了幻读的问题
- MySQL的SERIALIZABLE采用了经典的实现方式,对读和写都加锁。
- 因为使用mysql较多,这里对mysql进行相关小总结
- RC的并发效果好于RR,因为RC在where条件后将不符合条件的行解锁了
- RC的select能读取到所有已经提交的数据;RR的select只能读取到第一次select之前提交的数据。
- RC已经能满足绝大部分的功能。
死锁
- 死锁的发生与否,并不在于事务中有多少条SQL语句,死锁的关键在于:两个(或以上)的事务加锁的顺序不一致。(一个拿着id=1的锁,处理id=5的delete或update,另一个拿着id=5的锁,处理id=1的delete或update)
- Select …forupdate语句是我们经常使用手工加锁语句,如果使用of子句会对特定数据加锁,否则会对表加锁
- 所以在springmvc整合mybatis的时候有个事务处理机制,我一般会设置成处理完一个再处理另一个。
乐观锁
- 一般用在读很多的情况
- 指乐观的认为,在自己操作的时候别人不会update或delete数据,是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制
悲观锁
- 一般用在update或insert多的情况
- 每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
- eg:
select * from user where name=”zp” for update
这条 sql 语句锁定了 user 表中所有符合检索条件( name=”zp” )的记录。 本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。 - mysql悲观锁案例
- //我们可以使用命令设置MySQL为非autocommit模式:
set autocommit=0; - //0.开始事务
begin;/begin work;/start transaction; (三者选一就可以)
//1.查询出商品信息
select status from t_goods where id=1 for update;
//2.根据商品信息生成订单
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status为2
update t_goods set status=2;
//4.提交事务
commit;/commit work;
- //我们可以使用命令设置MySQL为非autocommit模式: