数据库事务
ACID
- A : Atomic:原子性:指数据库事务要么完成,要么不做.是一个不可分割的原子操作;
- C:consistency: 一致性:指事务完成要保证业务逻辑数据一致;即业务是A账户向B账户转账,无论怎么转,事务完成后必须保证A账户和B账户的总额不变;
- I:Isolation: 隔离性: 指不同事务有自己的执行空间,各自的操作不会影响到其他事务的执行;这里可以通过设置不同的隔离级别来控制事务的隔离强度,隔离级别越高,数据一致性越好,并发性能越低;
- D: Durability:持久性: 一旦事务成功提交,对数据的操作必须写进数据库中,即使是事务刚提交,数据库就崩溃,也要保证下一次重启时,之前的数据操作可以写到数据库中;
InnoDB对ACID的实现
事务的ACID是通过InnoDB日志和锁来保证。事务的隔离性是通过数据库锁的机制实现的,持久性通过redo log(重做日志)来实现,原子性和一致性通过Undo log来实现。UndoLog的原理很简单,为了满足事务的原子性,在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为UndoLog)。然后进行数据的修改。如果出现了错误或者用户执行了ROLLBACK语句,系统可以利用Undo Log中的备份将数据恢复到事务开始之前的状态。
和Undo Log相反,RedoLog记录的是新数据的备份。在事务提交前,只要将RedoLog持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是RedoLog已经持久化。系统可以根据RedoLog的内容,将所有数据恢复到最新的状态。
对具体实现过程有兴趣的同学可以去自行搜索扩展。
链接:https://juejin.im/post/5b5a0bf9f265da0f6523913b
数据库并发问题
- 脏读: A事务读取了B事务尚未提交的更改数据,并使用这个数据进行后续操作,但是B事务回滚,撤销了事务,B事务更改的数据就会被恢复为原来的值,但是A事务使用了被修改的值后又进行了一系列的操作;
- 不可重复读: 指A事务分别在B事务开始和结束后读取了被B修改的值,造成A事务的操作数据不一致;
- 幻读: 本事务一次select的查询结果不支持后续的操作,例如A事务先查询id=1不存在,接着B事务插入了id=1的数据,然后提交,然后A事务的插入id=1但是失败.
- 不可重复读和幻读的区别有很多误解,一般的理解是不可重复读指两次读取读到修改数据,而幻读是读到新增和删除的数据,一开始我也是这样认为的,但是又有一篇博客,说到,幻读并不是这样的,而是读到的不一致影响了后续操作才叫幻读,总之,点进去看吧
- 第一类丢失更新: 我觉得类似脏读,指A事务先读到一个值a,在后续操作前,B事务将该值修改为b,然后提交事务,然后A在原来读取到的值a的基础上继续执行,并做了修改为c,但是因为一些问题发生回滚,值被回滚到a,这时,B事务的操作就被丢失了,造成数据不一致;
- 第二类丢失更新: 类似第一类丢失更新,指A事务先读到一个值a,然后B事务开始并将a修改为b,提交事务,但是A事务继续执行,经a修改为c,这时丢失了B事务的更新;
事务隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 | 第一类丢失更新 | 第二类丢失更新 |
---|---|---|---|---|---|
READ UNCOMMITED | 可能 | 可能 | 可能 | 不可能 | 可能 |
READ COMMITED | 不可能 | 可能 | 可能 | 不可能 | 可能 |
REPEATABLE READ | 不可能 | 不可能 | 可能 | 不可能 | 不可能 |
SERIALIZABLE | 不可能 | 不可能 | 不可能 | 不可能 | 不可能 |
- 未提交读: 一个事务可以读到另一个未提交的事务修改的值;
- 提交读: 本事务读到的数据是最新的,也就是说一个事务内相同的query操作可以得到不同的结果(中间有其他事务提交)
- 可重复读: 本事务的相同的多次query操作得到的结果是一样的
- 串行读: 事务间有类似读写锁的锁机制,并发读允许,并发写不允许
- READ UNCOMMITED拥有最高的并发度和吞吐量,而SERIALIZABLE并发量最低;
- Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.
数据库如何实现隔离级别
- Read Uncommited : 读不加锁,写操作会加排它锁,具体看 这里
- Read Commited : 读使用MVCC,写加排他锁, 具体看 这里
- Repeatable Read : 默认情况下无法避免幻读,效果和RC相同,需要手动添加排他锁 for update,但是内部为了提高性能,并没有全部使用排它锁保证幻读,而是使用next-key lock ,具体看这里
数据库锁机制
按所对象分可以分为表锁定和行锁定,前者对整张表锁定,后者对特定行锁定,从并发事务关系来看可以分为共享锁和独占锁;共享锁允许其他共享锁进入,排斥独占锁,而独占锁即防止共享锁也防止独占锁;
行共享锁定:通过sql语句中
select ... for update
或者lock table in row share mode
(oracle)获得共享锁定,行独占锁定:通过sql语句
insert|update|delete
或者lock table in row exclusive mode
这些语句都是;表 共享锁定: 通过
lock table in share mode
,表共享可以防止行独占锁定和表独占锁定,允许表共享和行共享表共享行锁定 通过
lock table in share row exclusive mode
显式获得,这种锁定可以防止其他回话获取一个表共享,行独占,或者表独占;只允许行共享表独占: 通过
lock table in exclusive mode
语句显示获得,什么都不允许MyISAM使用的是表级锁, InnoDB使用的是行级锁
InnoDB行级锁算法
- Record Lock: 单行记录上的锁
- Gap lock: 间隙锁,锁定一段距离,不包括记录本身
- Next-key lock: record+gap,锁定一段距离,包括记录本身
- 三种锁的具体讲解,可以看这篇博客:InnoDB锁机制
- 使用next-key lock主要是为了解决幻读的问题,首先看上面对幻读的理解,事务1在执行期间,先获得信息是没有id=1的数据,正准备插入id=1的数据时,发现事务2已经插入了id=1的数据,导致之后的操作失败了,整个事务也就失败了
- RR解决幻读的问题是这样的的,还是上面的例子,事务1在查到id=1没有时,就会加上next-key lock锁住一段间隙,这样事务2就无法插入数据了,而如果数据存在,比如事务1先查数据存在,正准备删除时,会为这条记录加上写锁,导致其他事务无法先删除