MySql 锁和MVCC
MySql 锁和MVCC
数据库有四种隔离级别,级别越高,造成的事务并发执行的问题越少。
- 未提交读(Read Uncommitted):事务中的修改,即使没有提交,对其他事务也是可见的。
-
提交读(Read Committed):一个事务从开始到提交之前,所作的任何操作其他事务不可见。这个级别也叫不可重复读。
- 可重复读(Repeatable Read):保证同一事务中,多次读取同样记录的结果是一样的。是 mysql 默认的事务隔离级别。
- 可串行化(Serializable):强制事务串行执行,也就是加锁,避免幻读问题。事实上 InnoDB 通过多版本并发控制(multiversion concurrency control,MVCC )解决了幻读问题。
同时这里依次有事务并发执行的问题
- 脏读:事务可以读取未提交的事件。
- 不可重复读:同一事务中,多次读取同样记录之间如果其他事务更新数据,读取的结果可能会不一样。
- 幻读:同一事务中,多次读取之间如果其他事务插入新的记录,会出现幻行。
他们之间的关系
隔离级别 | 脏读可能性 | 不可重复读可能性 | 幻读可能性 |
---|---|---|---|
未提交读 | Yes | Yes | Yes |
提交读 | No | Yes | Yes |
可重复读 | No | No | Yes |
可串行化 | No | No | No |
锁
Mysql 中分为 表锁 和 行锁 ,行锁并发性能高。其中 MyISAM 支持表锁, InnoDB 支持表锁和行锁。
表锁更适用于以查询为主,只有少量按索引条件更新数据的应用;行锁更适用于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用。
读写锁
Mysql 表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock),也就是读写锁。
简单来说,除了读读,其他情况如果有一个持有写锁的事务,都会串行执行。
并发插入(Concurrent Inserts)
MyISAM存储引擎可以设置 concurrent_insert ,来控制其并发插入的模式
- 设为为0:不允许并发插入
- 设为为1(默认):表中没有空洞(即表的中间没有被删除的行),允许在一个进程读表的同时,另一个进程从表尾插入记录
- 设为为2:无论MyISAM表中有没有空洞,都允许在表尾并发插入记录
锁调度
MyISAM存储引擎在同时有请求时,会有一个请求队列,写锁的优先级大于读锁的优先级,因为写请求的操作要避免阻塞。
InnoDB 行锁
InnoDB存储引擎的行锁除了使用共享锁和排它锁之外,还使用了 意向共享锁 和 意向排它锁 。InnoDB行锁是通过给索引上的索引项加锁来实现的,只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁。
- 意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
- 意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。
除了意向锁内部相互兼容,对于共享锁和排它锁都是读写锁兼容模式
请求锁模式是否兼容当前锁模式 | X | S | IX | IS |
---|---|---|---|---|
X | 冲突 | 冲突 | 冲突 | 冲突 |
S | 冲突 | 兼容 | 冲突 | 兼容 |
IX | 冲突 | 冲突 | 兼容 | 兼容 |
IS | 冲突 | 兼容 | 兼容 | 兼容 |
有了意向锁,防止表锁级别上的请求冲突,就不需要全表扫描来寻找行锁了。
行锁会根据索引来锁定范围。对于键值在条件范围内但并不存在的记录,也会加锁,这种锁叫间隙锁。
MVCC
MVCC解决了幻读的问题,在查询的时候,看到的是一个 快照 版本。读写并存的时候,写操作会根据目前数据库的状态,创建一个新版本,并发的读则依旧访问旧版本的数据。这样就可以实现无锁。
在mysql中,有两个关于MVCC 的隐藏列,分别是
DB_TRX_ID
:数据行的版本号DB_ROLL_PT
:删除版本号
插入
InnoDB为新插入的每一行保存当前系统版本在 DB_TRX_ID
上。
删除
执行删除语句之后数据并没有被真正删除,而是对 DB_ROLL_PT
做改变
更新
先执行MVCC逻辑里面的删除流程,也就是改变原本数据的 DB_ROLL_PT
然后将新的数据插入进来,使用的是同一个 id
,但是 新数据的 DB_ROLL_PT
为 null
查询
首先查找数据行版本号( DB_ROLL_PT
)早于当前事务版本号的数据行记录,这样保证的查询的结果是查询之前的。
接下来查找删除版本号( DB_ROLL_PT
)要么为 null ,要么大于当前事务版本号的记录。这样保证的查询的结果,在事务开始之前未被删除。