数据库事务与锁详解


数据库事务

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隔离级别.

数据库如何实现隔离级别

  1. Read Uncommited : 读不加锁,写操作会加排它锁,具体看 这里
  2. Read Commited : 读使用MVCC,写加排他锁, 具体看 这里
  3. 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先查数据存在,正准备删除时,会为这条记录加上写锁,导致其他事务无法先删除

  目录