1.Mysql的innodb引擎,数据库表会自动增加三个字段(记录当前事务的版本号,记录删除时事务版本号,记录回滚指针地址)
2.数据库锁分为性质分为两类,悲观锁和乐观锁,共享锁(S锁)和排它锁(X锁)属于悲观锁,MVCC属于乐观锁(依靠事务的版本控制)
3.事务A对某行数据增加共享锁的时候,事务B可以继续增加共享锁,但是不能再增加排它锁,要等待所有共享锁都释放之后才能获加锁成功。当一个数据被加上排他锁之后,不能再加任何类型的锁,直到事务结束释放锁
4.read uncommitted级别下,数据库查询和更新数据库的时候都使用行级共享锁,所以就算没有提交,其他事务也能对当前数据加共享锁进行读取数据,导致脏读。
5. read committed级别下,数据库读取数据时使用共享锁,在更新数据时使用排它锁,提交完之后再释放排它锁,这样就解决了脏 读问题
6.reapetable read级别下用MVCC(多并发版本控制)解决了不可重复读问题,数据每次更新的时候,都会在数据行的隐藏字段中记录当前事务的版本号,当A事务第一次读取数据完之后未提交,此时版本号为1,期间事务B更新了该数据,并把版本号更改为2并提交,事务A再次读取时只会读取小于等于当前版本号的数据,不会读取到B事务的数据
7.Mysql的innodb引擎默认reapetable read级别,在该级别下MVCC能不能解决幻读呢?不能的话要如何解决幻读问题呢?
幻读是指多事务并发中一个事务读到了另一个事务insert的记录。
前提知识:
快照读:select操作(不包含select ...for update, select ..lock in share mode)
当前读:update,insert,delete,select ...for update, select ..lock in share mode
假设有如下场景:
# 表结构如下
CREATE TABLE hero (
number INT,
name VARCHAR(100),
country varchar(100),
PRIMARY KEY (number),
KEY idx_name (name)
) Engine=InnoDB CHARSET=utf8;
# 事务T1,REPEATABLE READ隔离级别下
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM hero WHERE number = 30;
Empty set (0.01 sec)
# 此时事务T2执行了:INSERT INTO hero VALUES(30, 'g关羽', '魏'); 并提交
mysql> UPDATE hero SET country = '蜀' WHERE number = 30;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SELECT * FROM hero WHERE number = 30;
+--------+---------+---------+
| number | name | country |
+--------+---------+---------+
| 30 | g关羽 | 蜀 |
+--------+---------+---------+
1 row in set (0.01 sec)
在REPEATABLE READ隔离级别下,T1第一次执行普通的SELECT语句时生成了一个ReadView,之后T2向hero表中新插入了一条记录便提交了,ReadView并不能阻止T1执行UPDATE或者DELETE语句来对改动这个新插入的记录(因为T2已经提交,改动该记录并不会造成阻塞),但是这样一来这条新记录的trx_id隐藏列就变成了T1的事务id,之后T1中再使用普通的SELECT语句去查询这条记录时就可以看到这条记录了,也就把这条记录返回给客户端了。因为这个特殊现象的存在,你也可以认为InnoDB中的MVCC并不能完完全全的禁止幻读。
总结:在RR模式下,快照读是通过MVCC和undo log来实现的,当前读是通过行锁+间隙锁来实现的
解决方案:在该级别下只能用行锁+间隙锁=next-key锁去解决幻读,具体操作如下:
间隙锁:间隙锁主要用来防止幻读,用在repeatable-read隔离级别下,指的是当对数据进行条件,范围检索时,对其范围内也许并存在的值进行加锁!当查询的索引含有唯一属性(唯一索引,主键索引)时,Innodb存储引擎会对next-key lock进行优化,将其降为record lock,即仅锁住索引本身,而不是范围!若是普通辅助索引,则会使用传统的next-key lock进行范围锁定!
幻读案例:有个表 (id 字段为非唯一辅助索引)每次插入前需查询这字段的最大值,然后再取最大值+1插入!
事务1 | 事务2 |
开启事务 | 开启事务 |
select max(id) from e; result:10 | insert into e values (11); commit; |
insert into e values (11); commit; 报错:ERROR 1062 (23000): Duplicate entry '11' for key 'id' |
在上述事务1中明明查询最大值为10,但插入最大值+1的时候却报错!
解决方案:利用mysql间隙锁
事务1 | 事务2 |
开启事务 | 开启事务 |
select max(id) from e lock in share mode; (此时会对id为10以上的所有不存在的值加间隙锁或共享锁) result:10 | insert into e values (11); commit; 尝试加排他锁 此时提交会一处于等待状态 |
在运用间隙锁的过程中,(-00 +00为负正无穷大)
如果条件为where a=5这样的条件,则间隙锁锁住的范围为(-00,3),(3,5),(5,6),(6,9),(9,+00)
如果条件为where a>5,则间隙锁锁住的范围为(5,+00)
如果为select max(id),则锁住的范围为(max(id),+00)
8.Serializable级别下,每个事务排队执行,可以解决四个问题