一 两段锁协议
因为数据库中有大量的并发访问,为了预防死锁,一般推荐使用一次封锁法,就是在方法的开始阶段预先知道使用哪些数据,然后全部锁住,在方法运行之后在解锁。可以避免死锁。但是数据库并不知道要用到哪些数据。
数据库遵循两段锁协议,将事物分成两个阶段。加锁阶段和解锁阶段。
加锁阶段:该阶段可以进行加锁操作,在任何数据进行读之前,都要申请并获得S锁(共享锁)。在写操作之前要获得X锁。加锁不成功,则事物进入等待状态,直到加锁成功才继续执行。
事务加锁/解锁处理
begin; insert into test .....加insert对应的锁
update test set...加update对应的锁
delete from test ....加delete对应的锁
commit;
事务提交时,同时释放insert、update、delete对应的锁。
这种方式无法避免死锁,但是两段锁可以保证事物的并发调度是串行的。
二 事物的四种隔离级别
在RC 这个隔离级别,数据的读取都是不加锁的,但是数据的写入,修改,删除是需要加锁的。
事务A
事务B
begin;
begin;
update class_teacher set class_name='初三二班' where teacher_id=1;
update class_teacher set class_name='初三三班' where teacher_id=1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
commit;
teacher_id 加了索引,锁住的是teacher_id 这一行,如果没有索引,锁住是是整个表。但实际使用过程中,MySql做了一些改进,在MySQL Server过滤条件发现不满足后,会调用unlock_row 方法把不满足条件的记录释放锁 (违背了二段锁协议的约束)。这样做,保证了最后只会持有满足条件记录上的锁,但是每条记录的加锁操作还是不能省略的。
RR 隔离级别
事务A
事务B
事务C
begin;
begin;
begin;
select id,class_name,teacher_id from class_teacher where teacher_id=1;
id
class_name
teacher_id
1
初三二班
1
2
初三一班
1
update class_teacher set class_name='初三三班' where id=1; commit;
insert into class_teacher values (null,'初三三班',1);commit;
select id,class_name,teacher_id from class_teacher where teacher_id=1;
id
class_name
teacher_id
1
初三二班
1
2
初三一班
1
很多人容易搞混不可重复读和幻读,确实这两者有些相似。但不可重复读重点在于update和delete,而幻读的重点在于insert。
在可重复读中,该SQL第一次读取到数据后就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复读了。但是无法锁住insert 数据。
三 MVCC协议
mySql 这种成熟的数据库出于性能考虑,使用了基于乐观锁的为理论基础的MVCC(协议)
在InnoDB中,会在每行数据后添加两个额外的隐藏的值来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期