MySQL一锅炖

88 阅读11分钟

一句话,redo日志用于保障,已提交事务的ACID特性。

一句话,undo日志用于保障,未提交事务不会对数据库的ACID特性产生影响。

MVCC就是通过“读取旧版本数据”来降低并发事务的锁冲突,提高任务的并发度。

undo日志和回滚段和InnoDB的MVCC密切相关

MVCC就是通过“读取旧版本数据”来降低并发事务的锁冲突,提高任务的并发度。

InnoDB的内核,会对所有row数据增加三个内部属性:

(1)DB_TRX_ID,6字节,记录每一行最近一次修改它的事务ID;事务ID

(2)DB_ROLL_PTR,7字节,记录指向回滚段undo日志的指针; 回滚指针

(3)DB_ROW_ID,6字节,单调递增的行ID; 自增id

(1)常见并发控制保证数据一致性的方法有锁,数据多版本;

(2)普通锁串行,读写锁读读并行,数据多版本读写并行;

(3)redo日志保证已提交事务的ACID特性,设计思路是,通过顺序写替代随机写,提高并发;

(4)undo日志用来回滚未提交的事务,它存储在回滚段里;

(5)InnoDB是基于MVCC的存储引擎,它利用了存储在回滚段里的undo日志,即数据的旧版本,提高并发;

(6)InnoDB之所以并发高,快照读不加锁;

(7)InnoDB所有普通select都是快照读;

总的来说,InnoDB共有七种类型的锁:

(1)自增锁(Auto-inc Locks);自增锁是一种特殊的表级别锁(table-level lock),专门针对事务插入AUTO_INCREMENT类型的列。

(2)共享/排它锁(Shared and Exclusive Locks); 共享锁 读读可以并行,

(1)事务拿到某一行记录的共享S锁,才可以读取这一行;

(2)事务拿到某一行记录的排它X锁,才可以修改或者删除这一行;

(3)意向锁(Intention Locks);

(4)插入意向锁(Insert Intention Locks);

(5)记录锁(Record Locks); 行锁

记录锁,它封锁索引记录,例如:select * from t where id=1 for update; 它会在id=1的索引记录上加锁,以阻止其他事务插入,更新,删除id=1的这一行。

普通select都是快照读; 不会加锁

(6)间隙锁(Gap Locks);间隙锁,它封锁索引记录中的间隔,或者第一条索引记录之前的范围,又或者最后一条索引记录之后的范围。

select * from t     where id between 8 and 15    for update;会封锁区间,以阻止其他事务id=10的记录插入。

为什么要阻止id=10的记录插入?

如果能够插入成功,头一个事务执行相同的SQL语句,会发现结果集多出了一条记录,即幻影数据。

间隙锁的主要目的,就是为了防止其他事务在间隔中插入数据,以导致“不可重复读”。

如果把事务的隔离级别降级为读提交(Read Committed, RC),间隙锁则会自动失效。

(7)临键锁(Next-key Locks);

【总结】

(1)自增锁(Auto-inc Locks):表级锁,专门针对事务插入AUTO_INC的列,如果插入位置冲突,多个事务会阻塞,以保证数据一致性;

(2)共享/排它锁(Shared and Exclusive Locks):行级锁,S锁与X锁,强锁;

(3)意向锁(Intention Locks):表级锁,IS锁与IX锁,弱锁,仅仅表明意向;

(4)插入意向锁(Insert Intention Locks):针对insert的,如果插入位置不冲突,多个事务不会阻塞,以提高插入并发;

(5)记录锁(Record Locks):索引记录上加锁,对索引记录实施互斥,以保证数据一致性;

(6)间隙锁(Gap Locks):封锁索引记录中间的间隔,在RR下有效,防止间隔中被其他事务插入;

(7)临键锁(Next-key Locks):封锁索引记录,以及索引记录中间的间隔,在RR下有效,防止幻读;

锁的类型有哪些

基于锁的属性分类:共享锁、排他锁。

基于锁的粒度分类:行级锁(INNODB)、表级锁(INNODB、MYISAM)、页级锁(BDB引擎 )、记录锁、间隙锁、临键锁。

基于锁的状态分类:意向共享锁、意向排它锁。

共享锁(Share Lock)

共享锁又称读锁,简称S锁;当一个事务为数据加上读锁之后,其他事务只能对该数据加读锁,而不能对

数据加写锁,直到所有的读锁释放之后其他事务才能对其进行加持写锁。共享锁的特性主要是为了支持

并发的读取数据,读取数据的时候不支持修改,避免出现重复读的问题。

排他锁(eXclusive Lock)

排他锁又称写锁,简称X锁;当一个事务为数据加上写锁时,其他请求将不能再为数据加任何锁,直到该

锁释放之后,其他事务才能对数据进行加锁。排他锁的目的是在数据修改时候,不允许其他人同时修

改,也不允许其他人读取。避免了出现脏数据和脏读的问题。

表锁

表锁是指上锁的时候锁住的是整个表,当下一个事务访问该表的时候,必须等前一个事务释放了锁才能

进行对表进行访问;

特点: 粒度大,加锁简单,容易冲突;

行锁

行锁是指上锁的时候锁住的是表的某一行或多行记录,其他事务访问同一张表时,只有被锁住的记录不

能访问,其他的记录可正常访问;

特点:粒度小,加锁比表锁麻烦,不容易冲突,相比表锁支持的并发要高;

记录锁(Record Lock)

记录锁也属于行锁中的一种,只不过记录锁的范围只是表中的某一条记录,记录锁是说事务在加锁后锁

住的只是表的某一条记录。

精准条件命中,并且命中的条件字段是唯一索引

加了记录锁之后数据可以避免数据在查询的时候被修改的重复读问题,也避免了在修改的事务未提交前

被其他事务读取的脏读问题。

页锁

页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突

少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。

特点:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般

间隙锁(Gap Lock)

属于行锁中的一种,间隙锁是在事务加锁后其锁住的是表记录的某一个区间,当表的相邻ID之间出现空

隙则会形成一个区间,遵循左开右闭原则。

范围查询并且查询未命中记录,查询条件必须命中索引、间隙锁只会出现在REPEATABLE_READ(重复

读)的事务级别中。

触发条件:防止幻读问题,事务并发的时候,如果没有间隙锁,就会发生如下图的问题,在同一个事务

里,A事务的两次查询出的结果会不一样。

比如表里面的数据ID 为 1,4,5,7,10 ,那么会形成以下几个间隙区间,-n-1区间,1-4区间,7-10

区间,10-n区间 (-n代表负无穷大,n代表正无穷大)

临建锁(Next-Key Lock)

也属于行锁的一种,并且它是INNODB的行锁默认算法,总结来说它就是记录锁和间隙锁的组合,临键锁

会把查询出来的记录锁住,同时也会把该范围查询内的所有间隙空间也会锁住,再之它会把相邻的下一

个区间也会锁住

触发条件:范围查询并命中,查询命中了索引。

结合记录锁和间隙锁的特性,临键锁避免了在范围查询时出现脏读、重复读、幻读问题。加了临键锁之

后,在范围区间内数据不允许被修改和插

入。

如果当事务A加锁成功之后就设置一个状态告诉后面的人,已经有人对表里的行加了一个排他锁

了,你们不能对整个表加共享锁或排它锁了,那么后面需要对整个表加锁的人只需要获取这个状态

就知道自己是不是可以对表加锁,避免了对整个索引树的每个节点扫描是否加锁,而这个状态就是

意向锁。

意向共享锁

当一个事务试图对整个表进行加共享锁之前,首先需要获得这个表的意向共享锁。

意向排他锁

InnoDB存储引擎的锁的算法

Record lock:单个行记录上的锁

Gap lock:间隙锁,锁定一个范围,不包括记录本身

Next-key lock:record+gap 锁定一个范围,包含记录本身

相关知识点:

  1. innodb对于行的查询使用next-key lock

  2. Next-locking keying为了解决Phantom Problem幻读问题

  3. 当查询的索引含有唯一属性时,将next-key lock降级为record key

  4. Gap锁设计的目的是为了阻止多个事务将记录插入到同一范围内,而这会导致幻读问题的产生

  5. 有两种方式显式关闭gap锁:(除了外键约束和唯一性检查外,其余情况仅使用record lock) A.

将事务隔离级别设置为RC B. 将参数innodb_locks_unsafe_for_binlog设置为1

.

脏读: 事务A读取到了事务B未提交的数据

不可重复读:事务A select 事务B update 并提交 事务A 再次select 读到事务B的数据 不可重复读, 两次读到的数据不一致

幻读: 每次读到的数据不一致

不同事务的隔离级别,实际上是一致性与并发性的一个权衡与折衷。

(1)读未提交(Read Uncommitted);

(2)读提交(Read Committed, RC);

(3)可重复读(Repeated Read, RR);

(4)串行化(Serializable)

三,可重复读(Repeated Read, RR)

这是InnoDB默认的隔离级别,在RR下:

(1)普通的select使用快照读(snapshot read),这是一种不加锁的一致性读(Consistent Nonlocking Read),底层使用MVCC来实现

(3)加锁的select(select ... in share mode / select ... for update), update, delete等语句,它们的锁,依赖于它们是否在唯一索引(unique index)上使用了唯一的查询条件(unique search condition),或者范围查询条件(range-type search condition):

    - 在唯一索引上使用唯一的查询条件,会使用记录锁(record lock),而不会封锁记录之间的间隔,即不会使用间隙锁(gap lock)与临键锁(next-key lock)

    - 范围查询条件,会使用间隙锁与临键锁,锁住索引记录之间的范围,避免范围间插入记录,以避免产生幻影行记录,尽量避免不可重复的读

四,读提交(Read Committed, RC)

这是互联网最常用的隔离级别,在RC下:

(1)普通读是快照读;

(2)加锁的select, update, delete等语句,除了在外键约束检查(foreign-key constraint checking)以及重复键检查(duplicate-key checking)时会封锁区间,其他时刻都只使用记录锁;

此时,其他事务的插入依然可以执行,就可能导致,读取到幻影记录

总结

(1)并发事务之间相互干扰,可能导致事务出现读脏,不可重复度,幻读等问题;

(2)InnoDB实现了SQL92标准中的四种隔离级别;

- 读未提交:select不加锁,可能出现读脏;

  • 读提交(RC):普通select快照读,锁select /update /delete 会使用记录锁,可能出现不可重复读;

- 可重复读(RR):普通select快照读,锁select /update /delete 根据查询条件情况,会选择记录锁,或者间隙锁/临键锁,以防止读取到幻影记录;

  • 串行化:select隐式转化为select ... in share mode,会被update与delete互斥;

(3)InnoDB默认的隔离级别是RR,用得最多的隔离级别是RC;

ACID 是靠什么保证的

A原子性 由undolog 日志保证, 它记录了 需要回滚的日志信息,事务回滚是撤销已经执行成功的sql

c一致性有其它三大特性保证 程序代码要保证业务上的一致性

I 隔离性由MVCC 机制实现

D持久性 由内存——redo log 来保证

InnoDB redo log 写盘,InnoDB 事务进入 prepare 状态。

如果前面 prepare 成功,binlog 写盘,再继续将事务日志持久化到 binlog,如果持久化成功,那么

InnoDB 事务则进入 commit 状态(在 redo log 里面写一个 commit 记录)