常见的是7种锁,还有一种不常见的预测锁
create table test(
id int not null,
name varchar(20) default null,
primary key (id)
)engine = InnoDB default CHARSET = utf8mb4 collate = utf8mb4_bin;
insert into test values (1,'zhagnsan'),
(5,'lisi'),
(10,'wangwu'),
(15,'zhaoliu'),
(20,null);
时间 | 窗口1 | 窗口2 |
---|---|---|
T1 | begin ; | |
T2 | begin; | |
T3 | select * from test where id = 11; | |
T4 | select * from test where id =12; | |
T5 | insert into test(id,name) values (11,‘test’); | |
T6 | insert into test(id,name) values (12,‘test’); | |
T7 | select * from test;(无12数据) | |
T8 | 锁等待状态 | select * from test;(无11数据) |
T9 | 锁等待接触 | 死锁,窗口2的事务被回滚了 |
解除死锁,查询数据
时间 | 窗口1 | 窗口2 |
---|---|---|
T1 | begin ; | |
T2 | begin; | |
T3 | select * from test where id = 11; | |
T4 | select * from test where id =12; | |
T5 | insert into test(id,name) values (11,‘test’); | |
T6 | insert into test(id,name) values (12,‘test’); | |
T7 | select * from test;(无12数据) | |
T8 | select * from test;(无11数据) | |
T9 | commit; | |
T0 | commit; |
并发状态下,两个事务都能提交,提交完成后不会产生死锁
行级锁(记录锁)是MySQL中锁定粒度最细的一种锁,表示单个数据上的锁,表示**单个行数据上的锁,行锁一定是作用在索引上的。**行级锁能够减少数据库操作的冲突,加锁粒度最小,但加锁的开销也是最大的
间隙锁,锁定一个范围,但不包括数据本身(它的锁粒度要比行级锁粒度更大一些,)它是锁定了某个范围内的多行数据,也可以加一行数据,
Gap锁的目的,为了防止同一事务的两次或者多次当前读,出现幻读的情况。该锁只会在隔离级别是可重复读级别出现的。间隙锁的目的就是无法让其他事务在间隙中新增数据。
是mysql中锁定粒度介于行级锁和表锁中间的一种锁,
页级锁特点:
是记录锁和间隙锁的结合,锁定一个范围,并且数据本身也在范围内的,对行的查询,采用临键锁,最主要解决了幻读的问题,
表级锁是mysql粒度最大的一种锁,表示当前操作的整张表的数据经进行加锁,资源消耗最小,但不灵活,常见的MYISAM和InnoDB都支持表级锁
表级锁的特点
开销小,加锁快,不会出现死锁,发生锁冲突的概率比较大,并发度最低;
读锁锁表,会阻塞其他事务修改表数据:lock table (表名) read;
写锁锁表,会阻塞其他事务读和写:lock table (表名) write;
不同存储引擎中的表级锁:
手动加锁
lock tables test read ;//读锁
lock tables test write ;//写锁
查看锁
show open tables;
删除锁
unlock tables;
共享锁/排他锁都是输入行锁,
读锁会阻塞写锁,但不会阻塞读,而写锁会把其他的线程的读和写都会阻塞掉
意向共享锁/意向排他锁属于表锁,且取得意向共享锁/意向排他锁取得共享锁的前置条件
他们的提出只是为了后期表级锁可以快速的判断表中的数据是否被上锁,以避免用遍历的凡是来查看锁中有没有上锁的记录,
插入意向锁是一种特殊的间隙锁,跟普通间隙锁不同是,该锁用于并发插入操作,如果间隙锁锁住的是一个区间,那么插入一项锁锁住的则是一个点
与间隙锁的差别:
悲观锁 认为对于同一个数据的并发操作,一定是会发生修改的(增删改多,查少),哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出问题。
悲观锁用的就是数据库的行锁,认为数据库会发生并发冲突,直接上来就把数据锁住,其他事务不能修改,直至提交了当前事务。
乐观锁则认为对同一个数据的并发操作,是不会发生修改的(增删改少,查多),在更新数据的时候,会采用不断尝试更新的方式来修改数据,也就是先不管资源有没有被别的线程占用,直接取申请操作,如果没有产生冲突,那就操作成功,如果产生冲突了,有其他线程已经在使用了,就会轮流使用,乐观的认为不加锁的并发操作是没有任何问题的,就是通过记录一个数据历史记录的多个版本,如果修改完之后发现有冲突再回到没有修改的样子,好处就是减少上下文切换,坏处就是浪费cpu时间。
利用数据版本号(version)机制是乐观锁最常见的实现方式,一般通过为数据库表增加一个数据类型的“version”字段,当读取数据时,将version字段的值一同读取,数据每更新一次,对此version值+1.
当提交更新的时候,判断数据库对应数据的当前版本信息,与第一次取出的version值进行对比,如果数据库表当前本版与第一次取出的version值相等,则更新,否则认为时过期数据,返回更新失败
乐观锁实现方式:
CAS机制会导致ABA问题
线程1修改数据值等于A,线程2修改数据后变成B,线程2再次修改数据后变成A,这样CAS检测时候,发现修改前后的值时一样的,就会认为没有修改,恢复原来值,
悲观锁的实现方式: