数据库的乐观锁和悲观锁?
MySQL 中有哪几种锁,列举一下?
MySQL中InnoDB引擎的行锁是怎么实现的?
MySQL 间隙锁有没有了解,死锁有没有了解,写一段会造成死锁的 sql 语句,死锁发生了如何解决,MySQL 有没有提供什么机制去解决死锁
锁,是计算机协调多个进程或者线程并发访问某一资源的机制
在数据库中,除了传统的计算资源的争用外,数据也是一种供需要用户共享的资源。数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问边得有序所设计的一种规则。
使用锁可以对有限的资源进行保护,解决隔离和并发的矛盾
锁的分类
从对数据操作的类型分类:
- 读锁(共享锁): 针对同一份数据,多个读操作可以同时进行,不会互相影响
- 写锁(排他锁):当前写操作没有完成前,她会阻断其他写锁和读锁。
从对数据操作的粒度分类
- 表级锁: 开销小,加锁快,不会出现死锁,锁定粒度大,发生锁冲突的概率最高,并发度最低
- 行级锁:开销大,加锁慢,会出现死锁,锁定粒度最小,发生锁冲突的概率最低,并发度也最高
- 页面锁:开销和枷锁时间解与表锁和行锁之间,会出现死锁,锁定粒度解与表锁和行锁之前,并发度一般
行锁 | 表锁 | 页锁 | |
---|---|---|---|
MyISAM | √ | ||
BDB | √ | √ | |
InnoDB | √ | √ | |
Memory | √ |
MyISAM表锁
myISAM的表锁有两种模式:
- 表共享读写:不会阻塞其他用户对同意表的读请求,但会阻塞对一个表的写请求
- 表独占写锁:会阻塞其他用户对同一表的读和写操作
MyISAM表的读操作和写操作之间,以及写操作之间是串行的,当一个线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作,其他线程的读写操作都会等待,直到锁被释放为止。
默认情况下,写锁比读锁具有更高的优先级,当一个锁释放时,这个锁会优先给写锁队列中等候的获取锁请求,然后再给读锁对联中等候的获取锁请求
InnoDB行锁
InnoDB实现了一下两种类型的行锁:
- 共享锁(s):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
- 排他锁(x):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁
为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB 还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁
-
意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的 IS 锁。
-
意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的 IX 锁。
索引失效会导致行锁变表锁。比如 vchar 查询不写单引号的情况
加锁机制
乐观锁与悲观锁时两种并发控制的思想,可用于解决丢失更新问题
乐观锁会“乐观地”假定大概率不会发生并发更新冲突,访问、处理数据过程中不加锁,只在更新数据时再根据版本号或时间戳判断是否有冲突,有则处理,无则提交事务。用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式
悲观锁会“悲观地”假定大概率会发生并发更新冲突,访问、处理数据前就加排他锁,在整个数据处理过程中锁定数据,事务提交或回滚后才释放锁。另外与乐观锁相对应的,悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以了。
锁模式(InnoDB有三种行锁的算法)
- 记录锁(Record Locks):单个行记录上的锁,对索引项枷锁,锁定符合条件的行,其他事务不能i需改和删除加锁项。
select * from table where id - 1 for update;
他会在id = 1 的记录上加上记录锁,以阻止其他事务插入,更新,删除id= 1 这一行。
在通过主键索引与唯一索引对数据行进行update操作时,也会对该行数据加记录锁
-- id列为主键列或者唯一索引列
update set age = 50 where id = 1;
- 间隙锁(Gap Locks):当我们使用范围条件而不是相等条件检索数据,并请求共享或排它锁时,InnoDB会给符合条件的已有数据记录的索引项加锁,对于键值在条件范围内但并不存在的记录,叫做“间隙”,InnoDB也会对这个间隙加锁,这种锁机制就是所谓的间隙锁
对索引项之间的“索引”加锁,锁定记录的范围(对第一条记录前的间隙或者最后一条记录后的间隙加锁)不包含索引本身,其他事务不能在锁范围内插入数据,这样就防止了别的事务新增幻影行,间隙锁基于非唯一索引,它锁定一段范围内的索引记录,间隙锁基于下面将会提到的Next-key locking算法,请务必牢记 :使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每条数据。
select * from table where id between 1 and 10 for update
即所在(1,10)区间内的记录行都会被锁住,所有id 为 2,3,4,5,6,7,8,9的数据行的插入都会被阻塞,但是1和10两条记录行并不会锁住。
GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。
- 临键锁(Next-key Locks):临键锁,是记录锁与间隙锁的组合,它的封锁范围,既包含索引记录,又包含索引区间,(临键锁的主要目的,也是为了避免幻读),如果把事务的隔离级别减低为RC,临键锁则也会失效
Next-key 可以理解为一种特殊的间隙锁,也可以理解为一种特殊的算法,通过临键锁可以解决幻读的问题,每个数据行上的非唯一索引列上都会存在一把临建锁,当某个事务持有该数据行的临建锁时,会锁住一段左开右闭得数据,需要强调的一点时,InnoDB中行级锁时基于索引实现的,临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。
select for update有什么含义,会锁表还是锁行还是其他
for update 仅适用于InnoDB,且必须在事务块(BEGIN/COMMIT)中才能生效,在进行事务操作时,通过 for update 语句,Mysql会对查询结果集中每行数据都添加排他锁。其他线程对该记录的更新于删除操作都会阻塞,排他锁包含行锁,表锁
InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁,假设有个表单products,里面有id跟name两个栏位,id是主键
明确指定主键,并且有此笔资料 rowlock 行锁
SELECT * FROM products WHERE id='3' FOR UPDATE;
SELECT * FROM products WHERE id='3' and type=1 FOR UPDATE;
明确指定主键,若查无此笔资料,无lock
select * from products where id = '-1' for update
无主键,table lock 表锁
SELECT * FROM products WHERE name='Mouse' FOR UPDATE;
主键不明确,table lock 表锁
select * from products where id<>'3' for update;
主键不明确,table lock 表锁
select * from products where id like '3' for update;
MySQL 遇到过死锁问题吗,你是如何解决的?
死锁
死锁产生:
- 死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环
- 当事务试图以不同的顺序锁定资源时,就可能产生死锁,多个事务同时锁定同一个资源时也可能会产生死锁。
- 锁的行为和顺序和存储引擎祥光,以同样的顺序执行语句,有些存储引擎会产生死锁有些不会,死锁有双重愿意:真正的数据冲突,存储引擎的实现方式
检测死锁: 数据库系统实现了各种死锁检测和死锁超时的机制,InnoDB存储引擎能检测到死锁的循环依赖并立即返回一个错误。
死锁恢复:死锁发生以后,只有部分或完全管回滚其中一个事务,才能打破死锁,InnoDB目前处理死锁的方法是,将持有最少行级排他锁的事务进行回滚,所以事务型应用在设计时必须考虑如何处理死锁,多数情况下只需要重新只能怪隐私所回滚的事务即可。
死锁影响性能:死锁会影响性能而不是会产生错误,i那位InnoDB会自动检测死锁状况并回滚其中一个受影响的事务,在高并发系统上,当许多线程等待同一个锁时,死锁检测可能导致速度变慢,有事当发生死锁时,禁用死锁检测可能会更有效,这时可以以来innodb_lock——wait_timeout 设置进行事务回滚
InnoDB避免死锁:
-
为了在单个InnoDB表上执行多个并发写入操作时避免死锁,可以在事务开始时通过为预期要修改的每个元组(行)使用
SELECT ... FOR UPDATE
语句来获取必要的锁,即使这些行的更改语句是在之后才执行的。 -
在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁,而不应先申请共享锁、更新时再申请排他锁,因为这时候当用户再申请排他锁时,其他事务可能又已经获得了相同记录的共享锁,从而造成锁冲突,甚至死锁
-
如果事务需要修改或锁定多个表,则应在每个事务中以相同的顺序使用加锁语句。在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样可以大大降低产生死锁的机会
-
通过
SELECT ... LOCK IN SHARE MODE
获取行的读锁后,如果当前事务再需要对该记录进行更新操作,则很有可能造成死锁。 -
改变事务隔离级别
如果出现死锁,可以用 show engine innodb status;
命令来确定最后一个死锁产生的原因。返回结果中包括死锁相关事务的详细信息,如引发死锁的 SQL 语句,事务已经获得的锁,正在等待什么锁,以及被回滚的事务等。据此可以分析死锁产生的原因和改进措施。