【MySQL】三、MySQL事务与锁详解

101 阅读5分钟

事务是关系型数据库的一大特性,用来保证数据的一致性。并发事务会导致数据不一致问题,MySQL针对并发事务也有相应的解决办法。下面一起来分析一下。

一、什么是数据库事务

1.1 事务的概念

维基百科的定义:事务是数据库管理系统(DBMS)执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。
这里面有两个关键点,第一个,它是数据库最小的工作单元,是不可以再分的。第二个,它可能包含了一个或者一系列的 DML 语句,包括 insert delete update。
(单条 DDL(create drop)和 DCL(grant revoke)也会有事务)

1.2 事务的四大特性 ACID

讲事务,就不得不提ACID原则。即:原子性、一致性、隔离性、持久性

1.2.1 原子性 Atomicity

事务不可再分。就是事务中的一组操作,要么全部完成,要么全部失败。在MySQL中,是通过 undo log 来实现原子性的。

1.2.2 一致性 Consistent

指的是数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据。
怎么理解呢?就是对数据库的操作,一定要符合预期。原子性、隔离性、持久性都是为了保证一致性。

1.2.3 隔离性 Isolation

隔离性是指不同事物间对相同数据的操作要隔离,不能相互影响。其实就可以同Java中的并发操作共享数据类比理解。意思差不多的。

1.2.4 持久性

持久性是指对数据的操作一定要生效,不会因为数据库宕机等外力因素导致没有生效。MySQL中通过 redo log + 双写缓冲实现持久性

二、事物隔离级别

2.1 并发事务产生的问题

2.1.1 脏读

事务1读取到了事务2未提交的数据。重点未提交

2.1.2 不可重复读

事务1读取到了事务2已提交(update或delete)的数据。

2.1.3 幻读

事务1读取到了事务2已提交(insert)的数据。
注意:幻读和不可重复读的区别是,幻读是读取到了其他事务insert的数据。

2.2 事务隔离级别

针对于并发事务引发的脏读、不可重复读和幻读的问题,MySQL有相应的隔离级别来解决。

2.2.1 读未提交 Read UnCommited

在RU下,可以读取到其他事务未提交的数据,会产生脏读问题。其实就是啥也没解决,啥也不是。。。

2.2.2 读已提交 Read Commited

在RC下,可以读取到其他事务已提交的数据,解决了脏读的问题,但是不可重复读和幻读的问题依然存在。

2.2.3 可重复读 Repeatable Read

MySQL默认的隔离级别。解决了脏读、不可重复读问题,同时基于 临键锁 也解决了幻读的问题。
临键锁:会锁住记录往后的区间,避免insert 比如:update user set name='帅比' where id = 7;
那么这时临键锁,会锁住 7 和 (7,+无穷),也就是说要想往7以后insert数据是没可能了,那么就不会发生幻读的问题了。

2.2.4 串行读 Serializable

不管你什么操作,统统加锁,毫无道理可言。当然效率最低。(一顿操作猛如虎,一看效率转圈中),哈哈。。。

三、锁

思考一个问题:如何实现事务的隔离?
其实这就跟我们解决Java的并发安全性问题一样。在Java中,我们可以怎么解决?

  1. CAS
  2. 加锁 那么在MySQL中,也同样有两种解决方法:
  3. Multi Version Concurrency Control(MVCC)
    InnoDB 为每行记录都实现了两个隐藏字段:
    DB_TRX_ID,6 字节:插入或更新行的最后一个事务的事务 ID,事务编号是自动递增的(我们把它理解为创建版本号,在数据新增或者修改为新数据的时候,记录当前事务 ID)。
    DB_ROLL_PTR,7 字节:回滚指针(我们把它理解为删除版本号,数据被删除或记录为旧数据的时候,记录当前事务 ID)
  4. Based Concurrency Control(LBCC) 这个就很好理解了。在操作数据的时候,先加锁,这样其它事务要想操作这个数据,比必须等先前的事务释放锁之后才能操作。

3.1 锁类型

锁的粒度:表锁、行锁 锁类型:

  • 共享锁,select * from user in share mode;
  • 排他锁, update/insert/delete默认就加了排他锁,或者select * from user for update;
  • 意向锁,MySQL隐式加的,在表记录加共享锁时,默认加了意向共享锁;排他锁同理。

3.2 锁算法

3.2.1 记录锁

等值查询,命中存在的记录。

3.2.2 间隙锁

查询没有命中已存在的记录。比如现在有id: 1、3、6、9这几条数据
select * from user where id in (7,8) for update;
没有命中,会锁住范围(7,8)

3.2.3 临键锁

查询包含存在的记录和不存在的记录。 select * from user where id >=6 for update;
包含已存在记录6,9,以及不存在的 7,8,这时锁范围:[6,9]和(9,+无穷)。
可以看到临键锁,会锁住最后一条记录往后的区间,这也是为什么RR隔离级别可以解决幻读的原因。

四、总结

  1. 事务主要是为了保证数据的一致性,而一致性是由原子性、隔离性、持久性共同来保证的。
  2. 原子性基于 undo log 来保证。
  3. 持久性基于 redo log + 双写缓冲来保证。
  4. MySQL在RR隔离级别下,保证了并发事务的安全性。主要是基于MVCC和LBCC两种解决方法。
  5. 在RR级别的临键锁,默认会锁住记录往后的下一个左开右闭的区间,所以能够解决幻读的问题。