一、为什么加锁
数据库中存在多个事务同时存取同一数据的情况,若对并发不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性,锁是用于管理对公共资源的控制。加锁解决了 多用户环境下保证数据库完整性和一致性。
二、在哪里加锁
使用锁的对象是事务,事务用来锁定数据库的对象是表、页、行。并且一般锁定的对象仅在事务commit或rollback后进行释放(不同事务隔离级别释放的时间可能不同)
三、有哪几种锁
1、锁粒度
1、行锁
表示只针对当前操作的行进行加锁,是锁定粒度最细的一种锁,但开销也做大,分为共享锁和排他锁。有可能会出现死锁。InnoDB默认的是行级锁。
2、表锁
是锁粒度最大的一种锁,开销也比较小Mysql的表级别锁分为两类:元数据锁(Metadata Lock,MDL)、表锁。不会出现死锁的情况, 元数据锁(MDL):不需要显式使用,在访问一个表的时候会被自动加上。这个特性需要MySQL5.5版本以上才会支持,当对一个表做增删改查的时候,该表会被加MDL读锁当对表做结构变更的时候,加MDL写锁。 MDL锁规则: 1、读锁之间不互斥 2、读写锁、写锁之间是互斥的,为了保证表结构变更的安全性,所以如果要多线程对同一个表加字段等表结构操作,就会变成串行化,需要进行锁等待 3、MDL的写锁优先级比MDL读锁的优先级高 4、MDL的锁释放必须要等到事务结束才会释放
3、页锁
页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。
2、兼容性
1、共享锁(s锁、读锁)
其他事务可以读,但是不能写。例如:此时事务T对数据对象A加上了共享锁,事务T可以读A,但是不能进行修改,其他事务也只能对A加共享锁不能加排他锁。这就保证了其他事务可以读A,但在事务T释放锁之前不能对A就行修改。
select ... lockinsharemode;
2、排他锁(x锁、写锁)
其他事务不能读取,也不能写。允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。 例如:若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。
select ... forupdate;
3、锁模式
1、意向共享锁(IS Lock)
表明事务想要获得一张表中某几行的共享锁。事务想要给其他数据行加共享锁,则必须先获得意向锁,InnoDB自动加的,不需要用户干预。
2、意向排他锁||互斥锁(IX Lock)
作用是表明事务想要获得一张表中某几行的排他锁 事务打算给数据行加行排他锁,那么就必须得先取得该表的 IX 锁。 为什么要有意向锁? 主要作用是处理行锁和表锁之间的矛盾,能够显示“某个事务正在某一行上持有了锁,或者准备去持有锁”,当我们需要加一个排他锁时,需要根据意向锁去判断表中有没有数据行被锁定。
举个例子,如果表中记录1亿,事务A把其中有几条记录上了行锁了,这时事务B需要给这个表加表级锁,如果没有意向锁的话,那就要去表中查找这一亿条记录是否上锁了。如果存在意向锁,那么假如事务A在更新一条记录之前,先加意向锁,再加X锁,事务B先检查该表上是否存在意向锁,存在的意向锁是否与自己准备加的锁冲突,如果有冲突,则等待直到事务A释放,而无须逐条记录去检测。事务B更新表时,其实无须知道到底哪一行被锁了,它只要知道反正有一行被锁了就行了。
3、Record Lock(单行记录)
锁住索引记录,记录锁的条件 1、命中单行记录并且命中的条件字段是唯一索引或者主索引; 例如: update user_info setname=’张三’ whereid=1;//这里的id是唯一索引,使用了Record Lock
4、间隙锁
间隙锁是封锁索引记录中的间隔,它能防止间隙内有新数据被插入。
为什么要有间隙锁 当不存在间隙锁的情况下,会有如下的场景: master库有这么两个事务: 1、事务a先delete id<6,然后在commit前; 2、事务b直接insert id=3,并且完成commit; 3、事务a进行commit; 此时binlog记录的日志是:事务b先执行,事务a在执行(binlog记录的是commit顺序) 那么主库此时表里面有id=3的记录,但是从库是先插入再删除,从库里面是没有记录的。 这就导致了主从数据不一致。 为了解决这个bug,所以RR级别引入了间隙锁。
记录锁是加在索引上的锁,间隙锁是加在索引之间的
5、Next-Key Lock(Record Lock + Gap Lock,临键锁)
Next-Key锁称为临键锁。 临键锁可以理解为锁住的是索引本身以及索引之前的间隙,是一个左开右闭的区间。当 SQL 执行按照非唯一索引进行数据的检索时,会给匹配到行上加上临键锁
为什么要加临键锁 临键锁的主要目的,也是为了避免幻读(Phantom Read)。如果把事务的隔离级别降级为RC,临键锁则也会失效。 在RR级别中不使用记录锁的情况下会默认使用的是Next-Key Lock
4、加锁机制
1、乐观锁
乐观锁假设数据一般情况不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果冲突,则返回给用户异常信息,让用户决定如何去做。乐观锁适用于读多写少的场景,这样可以提高程序的吞吐量。 乐观锁的实现: a、CAS 实现:Java 中java.util.concurrent.atomic包下面的原子变量使用了乐观锁的一种 CAS 实现方式。 b、版本号控制:一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会 +1。当线程 A 要更新数据时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值与当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。
乐观并发控制相信事务之间的数据竞争(datarace)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。
2、悲观锁
悲观锁,具有强烈的独占和排他特性。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度。因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制。 之所以叫做悲观锁,是因为这是一种对数据的修改持有悲观态度的并发控制方式。总是假设最坏的情况,每次读取数据的时候都默认其他线程会更改数据,因此需要进行加锁操作,当其他线程想要访问数据时,都需要阻塞挂起。
悲观锁的实现: 传统的关系型数据库使用这种锁机制,比如行锁、表锁、读锁、写锁等,都是在操作之前先上锁。 Java 里面的同步 synchronized 关键字的实现。
悲观锁主要分为共享锁和排他锁: 悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会。另外还会降低并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数据。
|