一、导言
关于MySQL中的锁还有隔离等级这类话题,其概念性的解释早已泛滥。记住其概念,算不上什么。更重要的是思考:他们的区别和联系在哪儿,为什么会有这样的概念。
1)MySQL的锁(Lock)分为行锁(Row Lock)和表锁(Table Lock),锁本身又分为读锁(Read Lock)和写锁(Write Lock)。
2)隔离等级分为Read uncommitted, Read committed, Repeatable reads, Serializable
3)在常见的读场景(Read phenomenon)中,会出现Dirty read, Non-repeatable reads以及Phantom Reads
为什么要这么分类,三个类别的概念是如何相互联系的。这里谈谈我的思考。
二、发现问题:Read phenomenon
所谓Dirty read,脏读,如下例所示
select age from students where id = 9
update table students set age = 12 where id = 9
select age from students where id = 9
rollback
脏读的关键在于,左侧的事务A中的第二次读操作读到了事务B并没有提交的数据,导致出错。关键点在于事务B还没有提交。
-------------------------------------------------------------------------------------------------------------------------------
所谓Non-repeatable reads,不可重复读,如下例所示
select age from students where id = 9
update table students set age = 12 where id = 9
commit
select age from students where id= 9
commit
不可重复读的关键,在于左侧的事务A中的第二次读操作读到了事务B中已经提交的数据,导致出错。关键点在于事务B已经提交。
-----------------------------------------------------------------------------------------------------------------------------------
Phantom Read,幻读,如下例所示
select * from students where id > 3 and id < 99
insert into students (id, name, age) values (12, "zs", 27)
commit
select * from students where id > 3 and id < 99
commit
幻读的关键,在于在集合统计层面上,出现了不可重复读,是一种特殊的不可重复读。不可重复读是修改数据,而幻读是新增数据。
-----------------------------------------------------------------------------------------------------------------------------------
那为什么要分为这三种层次的read phenomena?
这是因为,这三个层次实现了B事务的“缩小化”和“叠加”。从最开始B事务和A事务是交集状态,到后来B事务位于A事务内部, 再到最后由单个记录变为多条记录。本质上是这样一种递进关系。
二、分析问题:Isolation
为了依次避免上述每一个问题,就需要逐步采取更为严格的措施。这也就出现了隔离状态(Isolation)
1)啥都不避免,最烂的约束,就是Read uncommitted。这个uncommitted单词是有意味的,说白了就是未提交,也就是脏读中出现的情况。
2)避免脏读,就是Read committed。这个committed单词也是有意思的,是已提交。如此也就排除了脏读的情况,但是仍会有不可重复读和幻读的情况。
3)避免脏读和不可重复读,就是 Repeatable reads。对应的正好是non-repeatable。但是仍会有幻读情况。
4)避免所有情况,那就是Serializable。
三、解决解决:Lock
这样逐级进行分类,实际上也为解决这些问题提供了一种策略,那就是“锁”。
读锁(Read Lock)还有一个学名,叫做Shared Lock(共享锁)。写锁(Read Lock)也叫作排它锁(Exclusive Lock)
某线程一旦获得写锁,其他的线程都将无法获得任何锁,无论写锁还是读锁,都会被挂起,直到该写锁释放。
某线程获得了读锁,其他线程仍旧可以获得读锁,但无法获取写锁。直到读锁释放。
我们来锁看看是怎么解决上述read phenomena的
1)Read uncommitted。这个就是啥锁都不要。对吧。裸奔
2)Read committed。这个是要做到事务B能够完整提交,那好,就用一个写锁,保证事务B在执行过程中始终拥有一个写锁。至于事务A中的读,就不给锁了,或者换一个说法,每次select的时候会获得一个读锁,select操作完成后立即释放。总之读锁是不可能维持整个事务过程的。如此一来,避免了脏读,却无法避免不可重复读。
3)Repeatable reads。这下除了写锁,还得必须保证读锁了,也就是说,事务A必须拥有读锁,事务B必须有写锁,锁的生存期为整个事务过程。两者不可交叉。不可重复读的问题也解决了。
4)Serializable。如何克服统计层面上的幻读呢?现在我们不仅要保证单条数据记录的可重复读,还要保证多条记录在统计意义上的可重复读,那就是有采用表锁了(Table Lock),当然也不一定要锁全表,所以最为准确的说法,是用范围锁(Range Lock),把多条记录都上锁。讲到这里,也就很清楚了,1-3是不需范围锁的。
四、结语
锁,隔离等级,读场景,就是这么关联到了一起:发现问题,分析问题,解决问题。