一、概述
1、锁的定义:
锁是协调计算机协调多个线程或线程并发访问某一资源的机制
2、锁的分类:
按照不同的维度划分:
- 从对数据操作的的类型来分,分为,读锁和写锁。
- 从对数据操作的粒度来分,分为,表锁和行锁。
3、锁操作:
手动增加读表锁:lock table 表名称 read;
手动增加写表锁:lock table 表名称write;
查看是否加锁命令:show open tables
查看会显示In_use,Name_locked, 当表加锁后In_use会显示1,表示该表加锁了。
二、围绕不通存储引擎来分析锁机制
对于开销、加锁速度、死锁、粒度、并发性能,只能就具体应用的特点来说哪种锁更合适?
基本上是围绕三锁机制:
表锁(偏读):偏向MyISAM存储引擎,开销小,加锁快,无死锁,锁定粒度大,发生锁冲突的概率最高,并发度最低。
行锁(偏写):偏向InnoDB存储引擎,开销大,加锁慢,会出现死锁,锁定粒度小,发生锁冲突的概率最低,并发度也最高。
页锁:页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。BDB支持页级锁。
MyISAM与InnoDB区别:
InnoDB支持事物操作,采用行级锁,InnoDB其实也支持表级锁,当加锁或者索引使用不当的时候,行锁会失效变为表锁。
1、首先来分析基于MyISAM存储引擎的表锁
MyISAM的表锁从数据操作也分读锁和写锁,下面分别来看表锁下的读写锁情况:
(1)、读锁(共享锁)
读锁(共享锁):针对同一份数据,多个读操作可以同时进行,而不会互相影响。
案例分析:
lock table user1 read(给user1增加读锁)
现在有两个终端的session,分别为session1和session2;
执行过程:
session1可以select * from user1
session1不能update user1;会出错
session1不能查询和修改user2,也就是session1不能修改和查询其他的表
session2可以查询user1 也可以查询其他的user2等表。
session2不能修改user1,会阻塞,会一直等待,需要session1,unlock解锁 释放锁,才能修改。
结论:user1加了读锁后,可以查看自己的表,不能更新自己,也不能修改查询其他的表,而由于读锁是共享锁,其他(session2)终端是可以读被session1锁住的user1的,也可以查询其他的表,但是session2是不能修改被设置读锁的user1,执行更新语句会一直阻塞,处于等待的状态,直到session1的读锁被释放,才会更新。
(2)、写锁(排他锁)
写锁(排他锁):当前写操作没有完成前,它会阻断其他写锁和读锁。
案例分析:
lock table user1 (给user1增加写锁)
现在有两个终端的session,分别为session1和session2;
执行过程:
session1可以select * from user1
session1可以修改更新user1
session1不可以select * from 其他表,出报错。
session2可以查询其他表
session2不能select * from user1 会阻塞,处于等待的状态,必须释放session1的写锁
session2更不能对user1进行其他更新操作了。
结论:
user1加写锁后,session1可以对自己继续查看修改操作,但是不能对别的表进行查看和修改操作
session2不能查询和修改user1,需要等待user1的写锁被释放。
2、基于InnoDB的行锁分析
InnoDB的表锁从数据操作也分读锁和写锁,下面分别来看表锁下的读写锁情况:
案例分析:
首先我们来关闭自带的事物set autocommit=0
现在有两个终端的session,分别为session1和session2; 分别操作user1表(id,age)
第一种情况:
session1:update user1 set age=10 where id=1;
这时候session1查看user1表可以看到被修改的值,而session2查看user1还是原来的值,必须session1提交事物commit,session2才能查看到session1修改后的值。
第二种情况:
如果session1修改:update user1 set age=11 where id=1;这时候还没commit提交事物,session2执行相同的更新语句update user1 set age=12 where id=1;,会一直处于等待状态,也就是阻塞了,等session1 commit提交后,session2才能执行成功,并且session2要commit一次才能读到修改后的值。
第三种情况:
session1执行:update user1 set age=11 where id=1;
session2执行:update user1 set age=11 where id=2;
这种情况各自处理不同行的数据,不会有影响,各自操作自己的数据。
3、无索引行行锁升级为表锁
当索引使用不当时会导致行锁变为表锁
案例分析
已知user表,字段内容有id(int)和name(varchar)两个字段,分别都建立索引。
现在有两个终端的session,分别为session1和session2;
第一步:session1执行 update user set id=3 where name=4000;执行后正常,未提交事物前。
第二步:session2执行 update user set name='9002' where id=1;执行后出现阻塞。
针对第二步为啥会阻塞进行分析:
按道理来说session1和session2操作的数不同行的记录,应该互不影响呀!!!,问题在session1的update语句条件name是varchar类型,没有加单双引号,而索引会自动类型转化的, 像这种情况下行锁就变为表锁,性能就下降了,必须session1 commit后才可以。
4、间隙锁的危害
间隙锁的定义
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,innodb会符合条件的已有数据记录的索引项加锁,对于键值在条件范围内但并不存在的记录,叫间隙(GAP)。
案例分析
已知user表,字段内容有id(int)和name(varchar)两个字段,分别有记录id为,1,3,4,5,6,7 但是没有id=2的记录。
现在有两个终端的session,分别为session1和session2;
第一步:session1执行 update user set name='9002' where id>1 and id<6; 未提交事物
第二步:session2想把id=2的值inster插入进来,执行插入语句会出现阻塞。
间隙锁的危害
间隙锁有一个致命的弱点:
当锁定一个范围键值之后,即使某些不存在的键值,也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围的任何数据,在某些场景下这可能会对性能造成很大的危害。
三、乐观锁和悲观锁的思想
1、悲观锁
定义:
悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度(悲观),因此,在整个数据处理过程中,将数据处于锁定状态。 悲观锁的实现,往往依靠数据库提供的锁机制 (也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
悲观锁的执行流程:
- 在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。
- 如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。 具体响应方式由开发者根据实际需要决定。
- 如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。
- 其间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。
悲观锁优点与不足
悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会;另外,在只读型事务处理中由于不会产生冲突,也没必要使用锁,这样做只能增加系统负载;还有会降低了并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数。
2、乐观锁
定义
乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。
乐观锁的执行流程
相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本。
数据版本,为数据增加的一个版本标识。当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,如果数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,否则认为是过期数据。
实现数据版本有两种方式,第一种是使用版本号,第二种是使用时间戳。
乐观锁优点与不足
乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。但如果直接简单这么做,还是有可能会遇到不可预期的结果,例如两个事务都读取了数据库的某一行,经过修改以后写回数据库,这时就遇到了问题。