由于数据库中有大量的并发访问,为了预防死锁,通常推荐使用一次封锁法,就是在方法的开始阶段预先知道使用哪些数据,而后所有锁住,在方法运行以后在解锁。能够避免死锁。可是数据库并不知道要用到哪些数据。数据库
数据库遵循两段锁协议,将事物分红两个阶段。加锁阶段和解锁阶段。并发
加锁阶段:该阶段能够进行加锁操做,在任何数据进行读以前,都要申请并得到S锁(共享锁)。在写操做以前要得到X锁。加锁不成功,则事物进入等待状态,直到加锁成功才继续执行。性能
事务加锁/解锁处理spa
begin; insert into test .....加insert对应的锁rest
update test set...加update对应的锁索引
delete from test ....加delete对应的锁事务
commit;it
事务提交时,同时释放insert、update、delete对应的锁。io
这种方式没法避免死锁,可是两段锁能够保证事物的并发调度是串行的。table
在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;
|
|||||||||||
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;
|
不少人容易搞混不可重复读和幻读,确实这二者有些类似。但不可重复读重点在于update和delete,而幻读的重点在于insert。
在可重复读中,该SQL第一次读取到数据后就将这些数据加锁,其它事务没法修改这些数据,就能够实现可重复读了。可是没法锁住insert 数据。
mySql 这种成熟的数据库出于性能考虑,使用了基于乐观锁的为理论基础的MVCC(协议)
在InnoDB中,会在每行数据后添加两个额外的隐藏的值来实现MVCC,这两个值一个记录这行数据什么时候被建立,另一个记录这行数据什么时候过时