为什么要加锁
目的是为了保证数据的一致性。 当多个线程并发访问某个数据时,加锁可以保证这个数据在任何时刻最多只有一个线程在访问,保证数据的完整性和一致性。
锁的分类
按照不同的分类方式,有不同的锁
1、按照锁的粒度分类
表级锁:在表的粒度上进行锁定,开销小,加锁快,锁定粒度大,发生锁冲突最高,并发度最低
行级锁:在行的粒度上进行锁定,是MySQL中锁定粒度最细的锁。InnoDB引擎支持行级锁和表级锁,只有在通过索引条件检索数据的时候,才使用行级锁,否就使用表级锁。行级锁开销大,加锁慢,锁定粒度最小,发生锁冲突的概率最低,并发度最高。
页级锁:在页的粒度上进行锁定,锁定的数据资源比行锁要多,比表锁要少,页锁的开销、加速速度、发生概率冲突和并发度介于表锁行锁之间。
InnoDB 和 Oracle 支持行锁和表锁,MyISAM 只支持表锁, MYSQL BDB 存储引擎支持页锁和表锁。SQL Server 可以支持行锁,页锁和表锁。
2、按照数据库管理角度分类
读锁:共享锁、S锁,读锁锁定的资源可以被其他用户读取,但不能修改。 在进行 SElECT 的时候,会将对象进行共享锁锁定,当数据读取完毕之后,就会释放共享锁,这样就可以保证数据在读取时不被修改。读-读不互斥,但读-写互斥。
写锁:独占锁、排他锁、X锁,写锁锁定的数据只允许进行锁定操作的事务使用,其他事务无法对已锁定的数据进行查询或者修改。读-写互斥,写-写互斥。
3、从程序员角度划分
乐观锁:乐观锁认为对同一个数据并发操作不会总发生,是小概率事件,因此不用每次对数据进行更新或者删除。通过版本号或者时间戳来实现。适用于写少读多场景。
悲观锁:通过数据库自身的锁机制来实现,从而保证数据操作的排他性。适用于写多读少的场景。传统的关系型数据库使用这种锁机制,比如行锁、表锁、读锁、写锁等,都是在操作之前先上锁。
Java 里面的同步 synchronized 关键字的实现。
其他锁概念:意向锁、间隙锁
意向锁:表级锁在获得一个锁之前,会先申请获得其意向锁,来阻塞其他冲突的锁请求。意向锁可分为意向共享锁IS和意向排他锁IX。假设事物A获得一个行锁,此时事物B想获得一个表锁,如果没有意向锁,则事务B需要先判断有没有冲突的表锁,然后判断表中每一行有没有冲突的行锁,这种方式效率很低。而有了意向锁之后,意向锁作为一个表级锁,可以极高效率地判断锁冲突情况。申请意向锁的动作是数据库完成的,即事务申请行锁的时候,数据库会自动先开始申请表的意向锁,不需要我们程序员使用代码来申请。
间隙锁:Next-Key锁,当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是间隙锁(行锁+Gap锁)。比如:
exam表中只有101条记录,其examid的值分别是 1,2,…,10,11,下面的SQL:
Select * from exam where examid > 10 for update;
这是一个范围条件的检索,InnoDB不仅会对符合条件的examid值为11的记录加锁,也会对empid大于11(这些记录并不存在)的"间隙"加锁。
使用间隙锁的目的:
1、防止幻度,以满足相关隔离级别的要求。对于上面的例子,要是不使用间隙锁,如果其他事务插入了examid大于11的任何记录,那么本事务如果再次执行上述语句,就会发生幻读;
2、为了满足复制和恢复的需要。
在使用范围条件检索并锁定记录时,InnoDB这种加锁机制会阻塞符合条件范围内键值的并发插入,这往往会造成严重的锁等待。因此,在实际应用开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件。
InnoDB除了通过范围条件加锁时使用间隙锁外,如果使用相等条件请求给一个不存在的记录加锁,InnoDB也会使用间隙锁!假设exam表中只有11条记录,其examid的值分别是1,2,……,10,11。 事务1执行Select * from exam where examid =12 for update,当事务1对不存在的记录12加 for update时,这时如果有其他事物插入examid=12的记录(该记录并不存在),也会出现等待。
浙公网安备 33010602011771号