1、锁
1.1 什么情况下会加锁?
读分为 当前读 跟 快照读。 快照读可以直接读取历史版本,不需要加锁,而 当前读每次都是读取最新版本,所以需要加锁。
其中 update/ delete / Insert都属于 当前读,因为得先查出来再更新,或者删除。 insert 可能会触发 unique key 的冲突检查,也会进行一个当前读。
另外 select for update 跟 select in share mode 都属于当前读。
1.2 锁是什么?
1.3 锁的对象是什么?
Lock的对象是事务,用来锁定的是数据库中的对象,如表、页、行。并且一般lock的对象仅在事务commit或rollback后进行释放(不同事务隔离级别释放的时间可能不同)。
1.4 锁的分类?
1.4.1 行锁与表锁
1、行锁的分类
* LOCK_ORDINARY:也称为 **Next-Key Lock**,锁一条记录及其之前的间隙,这是 RR 隔离级别用的最多的锁,从名字也能看出来;
* LOCK_GAP:间隙锁,锁两个记录之间的 GAP,防止记录插入;
* LOCK\_REC\_NOT_GAP:只锁记录;
* LOCK\_INSERT\_INTENSION:插入意向 GAP 锁,插入记录时使用,是 LOCK_GAP 的一种特例。
1.4.2 锁模式:读锁/写锁/读写意向锁
1、读锁、写锁
2、读写意向锁 相加表写锁时,不需要逐个遍历表中所有的行上面是否有 写锁,或者读锁。
3、AUTO_INC 锁 (自增锁) 表锁,当插入的表中有自增列时,数据库自动生成自增值,在生成之前,会先为该表加 AUTO_INC 表锁,其他事务的插入操作阻塞。
1.5 事务存在的几个并发问题
脏读:读到未提交的数据。 不可重复读:因为其他事务更新,两次读取的内容不一样(记录数一致) 幻读:因为其他事务插入或者删除,两次读取的记录数不一致。 丢失更新:多个事务同时更新,到时其中一个事务的更新丢失了。
1.6 事务的隔离级别与锁
(1)Read Uncommited 此级别不会使用到。忽略。
事务读不阻塞其他事务读和写,事务写阻塞其他事务写但不阻塞读;通过对写操作加 “持续X锁”,对读操作不加锁 实现;
其中 读未提交 网上有很多人认为不需要加任何锁,这其实是错误的,我们上面讲过,有一种并发问题在任何隔离级别下都不允许存在,那就是第一类丢失更新(回滚覆盖),如果不对写操作加 X 锁,当两个事务同时去写某条记录时,可能会出现丢失更新问题
(2)Read Commited 快照读忽略。 当前读,保证对读取到的记录加锁(记录锁),存在幻读现象。
它是为了解决脏读问题,只能读取已提交的记录,要怎么做才可以保证事务中的读操作读到的记录都是已提交的呢?很简单,对读操作加上 S 锁,这样如果其他事务有正在写的操作,必须等待写操作提交之后才能读,因为 S 和 X 互斥,如果在读的过程中其他事务想写,也必须等事务读完之后才可以。
(3)Repeatable Read 快照读忽略。 当前读,保证对读取到的记录加锁(记录锁),同时保证对读取的范围加锁,新的满足查询条件的记录不能够插入(间隙锁),不存在幻读。
为了让事务可以重复读,加在读操作的 S 锁变成了持续 S 锁,也就是直到事务结束时才释放该锁,这可以保证整个事务过程中,其他事务无法进行写操作,所以每次读出来的记录是一样的。
(4)Serializable 从 MVCC 退化为基于锁的并发控制。所有的读操作均为当前读,读加读锁,写加写锁。
序列化 隔离级别下单纯的使用行锁已经实现不了,因为行锁不能阻止其他事务的插入操作,这就会导致幻读问题,这种情况下,我们可以把锁加到表上(也可以通过范围锁来实现,但是表锁就相当于把表的整个范围锁住,也算是特殊的范围锁吧)。
1.7 加锁逻辑?需要区分多种情况
1.7.1 语句使用了什么锁?
www.aneasystone.com/archives/20… 强烈推荐这篇文章。
1、前提条件: (1)隔离级别是什么? (2)查询列是否为主键? (3)查询列上是否有索引 (4)查询列上是否有二级索引,那么这个二级索引是唯一索引吗? (5)sql 的执行计划是?索引扫描?全表扫描?
2、select * from t where id = 1 都是快照读,所以不会上锁。
3、delete from t where id = 1 分析 (1)id 主键,RC 隔离级别 id =1 上加 x 锁即可。
(2)id 二级唯一索引,RC 隔离级别 t 的结构(name primary key, id unique key)
二级索引上加 x 锁,且聚簇索引上对应的行 也需要 加 X 锁。
(3)id 二级非唯一索引,RC 隔离级别 对应所有满足条件的非唯一索引上都加记录锁,同时这些索引对应的聚集索引上也加记录锁。
(4) id 上没有索引,RC 隔离级别 sql 走聚簇索引的全表扫描进行过滤,由于过滤是由 MySQL Server 层进行的,因此每条记录,无论是否满足条件,都会被加上 X 锁。为了优化,不满足的记录会立马释放锁,最终持有的是满足条件的锁,但是不满足条件的记录上的加锁、放锁动作不会省略。
(5)id 主键,RR 隔离级别 id =1 上加 x 锁即可。
(6)id 二级唯一索引,RR 隔离级别 t 的结构(name primary key, id unique key)
二级索引上加 x 锁,且聚簇索引上对应的行 也需要 加 X 锁。
(7)id 二级非唯一索引,RR 隔离级别
首先找到第一条满足条件的记录,加记录上的 X 锁,加 GAP 上的 GAP 锁,然后加聚簇索引上的记录 X 锁,然后返回;然后读取吓一跳,重复进行。
(8) id 上没有索引,RR 隔离级别 会锁上表中的所有记录,同时会锁上聚簇索引内的所有 GAP,杜绝所有的并发 更新、删除、插入操作。
(9)Serializable 隔离级别 所有的读操作 都要加锁了。
1.8 死锁
(1)产生的原因 (2)如何检查 跟避免?
1.9 MVCC
9.1 简介
MVCC 的全称叫做 Multi-Version Concurrent Control(多版本并发控制),InnoDb 会为每一行记录增加几个隐含的“辅助字段”,(实际上是 3 个字段:一个隐式的 ID 字段,一个事务 ID,还有一个回滚指针),事务在写一条记录时会将其拷贝一份生成这条记录的一个原始拷贝,写操作同样还是会对原记录加锁,但是读操作会读取未加锁的新记录,这就保证了读写并行。要注意的是,生成的新版本其实就是 undo log,它也是实现事务回滚的关键技术。
9.2 MVCC 适用的隔离级别
只有 RC 和 RR 这两个隔离级别才有 MVCC
RC 总是读取记录的最新版本,如果该记录被锁住,则读取该记录最新的一次快照,而 RR 是读取该记录事务开始时的那个版本。