MySQL中的锁与事务
锁
对于 MyISAM 存储引擎,只支持表级锁,对于 InnoDB 来说,既支持表级锁、也支持行级锁。所以 InnoDB 可以用于高并发的场景下而 MyISAM 不行。
按锁的颗粒度划分
-
行锁
对一行数据加锁,当一个事务操作某一行事务时,只对该行数据加排他锁时,其他事务对其他行数据操作时不会影响,并发性好。缺点是在加多条数据时加锁会比较耗时。一个事务获取到锁后直到事务提交才会释放锁。 -
表锁
包含两种:
1、MDL,是 DDL 操作与 DML、DQL 操作冲突的加锁。
2、对整张表进行加读写锁,仅针对 DML、DQL 操作。加锁快但是可承受的并发量低。 -
全局锁
对所有表所有数据进行加锁。这个锁是读锁,也就是加锁后当前数据库只能处理读操作,不能处理写操作。一般在将整个库的数据进行逻辑备份时使用(在 InnoDB 中可以使用 mysqldump 进行非阻塞式备份,原理就是通过 隔离级别MVCC数据一致性实现的)。 -
页级锁
对一页数据进行加锁,介于行级锁与表级锁之间。
行锁的种类
InnoDB存储引擎实现了如下两种标准的行级锁:
- 共享锁(S Lock):允许事务读取一行数据
- 排他锁(X Lock):允许事务删除或者更新一行数据
如果一个事务T1已经获得了行r的共享锁,那么另外的事务T2可以立即获得行r的共享锁,因为读取并没有改变行r的数据,称这种情况为锁兼容(Lock Compatible)。但若有其他的事务T3想获得行r的排他锁,则其必须等待事务T1、T2释放行r上的共享锁——这种情况称为锁不兼容。
X | S | |
---|---|---|
X | 不兼容 | 不兼容 |
S | 不兼容 | 兼容 |
此外, InnoDB存储引擎支持多粒度(granular)锁定,这种锁定允许事务在行级上的锁和表级上的锁同时存在。为了支持在不同粒度上进行加锁操作, InnoDB存储引擎支持一种额外的锁方式,称之为意向锁(Intention lock)。意向锁是将锁定的对象分为多个层次,意向锁意味着事务希望在更细粒度(fine granularity)上进行加锁。
若将上锁的对象看成一棵树,那么对最下层的对象上锁,也就是对最细粒度的对象进行上锁,那么首先需要对粗粒度的对象上锁。如果需要对页上的记录r进行上X锁,那么分别需要对数据库A、表、页上意向锁Ⅸ,最后对记录r上X锁。若其中任何一个部分导致等待,那么该操作需要等待粗粒度锁的完成。举例来说,在对记录r加X锁之前,已经有事务对表1进行了S表锁,那么表1上已存在S锁,之后事务需要对记录r在表1上加上IX,由于不兼容,所以该事务需要等待表锁操作的完成。
InnoDB存储引擎支持意向锁设计比较简练,其意向锁即为表级别的锁。设计目的主要是为了在一个事务中揭示下一行将被请求的锁类型。
其支持两种意向锁:
- 意向共享锁(IS Lock),事务想要获得一张表中某几行的共享锁
- 意向排他锁(IX Lock),事务想要获得一张表中某几行的排他锁由于InnoDB存储引擎支持的是行级别的锁,因此意向锁其实不会阻塞除全表扫以外的任何请求。
各种锁的兼容性如下:
IS | IX | S | X | |
---|---|---|---|---|
IS | 兼容 | 兼容 | 兼容 | 不兼容 |
IX | 兼容 | 兼容 | 不兼容 | 不兼容 |
S | 兼容 | 兼容 | 兼容 | 不兼容 |
X | 不兼容 | 不兼容 | 不兼容 | 不兼容 |
行锁的3种算法
- Record Lock:单个行记录的锁
- Gap Lock:间隙锁,锁定一个范围,但是不包含记录本身
- Next-Key Lock: Gap Lock + Record Lock,锁定一个范围,并且锁定记录本身
Record Lock总是会去锁住索引记录,如果 InnoDB存储引擎表在建立的时候没有设置任何一个索引,那么这时 nodE存储引擎会使用隐式的主键来进行锁定。
Next-Key Lock是结合了 Gap Lock和 Record Lock的一种锁定算法,在 Next-KeyLock算法下, InnoDB对于行的查询都是采用这种锁定算法。
事务
事务的定义
事务是用户定义的一系列数据库操作,这些操作可以视为一个完成的逻辑处理工作单元,要么全部执行,要么全部不执行,是不可分割的工作单元。
事务的四大特性
- 原子性:原子性指整个数据库事务是不可分割的工作单位。只有使事务中所有的数据库操作都执行成功,才算整个事务成功。事务中任何一个SQL语句执行失败,已经执行成功的SQL语句也必须撤销,数据库状态应该退回到执行事务前的状态。
- 一致性:一致性指事务将数据库从一种状态转变为下一种一致的状态。在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。
- 隔离性:一个事务的执行不能被其他事务干扰。事务的隔离性要求每个读写事务的对象对其他事务的操作对象能相互分离,即该事务提交前对其他事务都不可见。
- 持久性:事务一旦提交,其结果就是永久性的。即使发生宕机等故障,数据库也能将数据恢复。需要注意的是,只能从事务本身的角度来保证结果的永久性。
事务四大特性的实现
隔离性的问题
通过锁定机制可以实现事务的隔离性要求,使得事务可以并发地工作。锁提高了并发,但是却会带来潜在的问题。
-
脏读:脏读指的就是在不同的事务下,当前事务可以读到另外事务未提交的数据,简单来说就是可以读到脏数据。(读取未提交)
-
不可重复读:不可重复读是指在一个事务内多次读取同一数据集合。在这个事务还没有结束时另外一个事务也访问该同一数据集合,并做了一些DML操作。因此,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的情况,这种情况称为不可重复读。
不可重复读和脏读的区别是:脏读是读到未提交的数据,而不可重复读读到的却是已经提交的数据,但是其违反了数据库事务一致性的要求。(读取提交了的)
-
幻读:幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
不可重复度和幻读区别:不可重复读的重点是修改,幻读的重点在于新增或者删除。
例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导 致A再读自己的工资时工资变为 2000;这就是不可重复读。
例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读。
- 丢失更新:丢失更新是另一个锁导致的问题,简单来说其就是一个事务的更新操作会被另一个事务的更新操作所覆盖,从而导致数据的不一致。例如:
1)事务T1将行记录r更新为v1,但是事务T1并未提交
2)与此同时,事务T2将行记录r更新为v2,事务T2未提交。
3)事务T1提交
4)事务T2提交
事务的隔离级别
- READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
- READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
- REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读,但是效率也最低。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ-UNCOMMITTED | √ | √ | √ |
READ-COMMITTED | × | √ | √ |
REPEATABLE-READ | × | × | √ |
SERIALIZABLE | × | × | × |
InnoDB存储引擎默认支持的是REPEATABLE-READ(可重复读)级别的隔离,而Oracle的默认级别是Read Committed(读取已提交)
InnoDB引擎在REPEATABLE-READ(可重复读)级别通过间隙锁解决了幻读问题,通过MVCC解决了不可重复读的问题