抛砖引玉:多个查询需要在同一时刻进行数据的修改,就会产生并发控制的问题。我们需要如何避免写个问题从而保证我们的数据库数据不会被破坏。
锁的概念
读锁是共享的互相不阻塞的。多个事务在听一时刻可以同时读取同一资源,而相互不干扰。
写锁的排他的。一个写锁会阻塞其他写锁或读锁。出于安全考虑只有这样才能保证在给定的时间里只有一个事务能够执行写入,并防止其他事务读取正写入的同一资源。
锁带来的问题
通过锁定机制可以实现事务的隔离性要求,使得事务可以并发的工作,同时也带来了三个问题:脏读,不可重复读和丢失更新。
脏读
脏数据:未提交的数据
如果读到了脏数据即一个事务可以读取到另一个事务中未提交的数据那就违背了事务的隔离性。
所以脏读是指在不同的事务下,当前事务可以读取到另外事务的未提交的数据,简单来说就是可以读取到脏数据。
演示:
初始状态:
将会话A,B设置隔离级别为RU
sql
set session transaction isolation level READ UNCOMMITTED;
会话A插入一条数据
这时候事务B在此执行查询操作,会发现事务B读取到了事务A新增的数据。注意:此时事务A没有提交。
不可重复读
在一个事务中两次读取到的数据是不一样的,这中情况被称为不可重复读。
与脏读的区别:脏读是读取到了未提交的数据,而不可重复读是读取到的却是已经提交的数据,但是违反了数据库事务一致性的要求。
演示:
事务B插入一条数据并且提交
事务A在此执行select语句,事务A读取到了事务B提交的数据
一般来说不可重复读问题是可以接受的,因为读取到的是已经提交的数据,本身不会带来什么问题。例如Oracle 和 SQL Server的默认的事务隔离级别就是RC。 MySQL默认的事务隔离级别是RR。
在MySQL InnoDB中通过使用 Next-key lock 算法来避免不可重复读问题,并且将不可重复读问题定义为幻读(Phantom problem)
丢失更新
一个数据的更新会被另一个事务的更新操作所覆盖,从而导致数据的不一致性。
第一种丢失:
A事务撤销时,把已经提交的B事务的更新数据覆盖了。这种错误可能造成很严重的问题,通过下面的账户取款转账就可以看出来:
时间 | 取款事务A | 转账事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 汇入100元把余额改为1100元 | |
T6 | 提交事务 | |
T7 | 取出100元把余额改为900元 | |
T8 | 撤销事务 | |
T9 | 余额恢复为1000 元(丢失更新) |
第二类丢失更新
A事务覆盖B事务已经提交的数据,造成B事务所做操作丢失:
时间 | 转账事务A | 取款事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 取出100元把余额改为900元 | |
T6 | 提交事务 | |
T7 | 汇入100元 | |
T8 | 提交事务 | |
T9 | 把余额改为1100 元(丢失更新) |
要避免丢失更新的发生,需要让事务在这种情况的操作变成串行化,而不是并行操作。即上面的select操作中加上排他锁。
MySQL锁的分类:
按照锁的粒度来说
MySQL主要包含三种类型(级别)的锁定机制:
全局锁:锁的是整个database。
表级锁:锁的是某个table。 (表排他锁,表共享锁,元数据锁,自增锁)
行级锁:锁的是某行数据,也可能锁定行之间的间隙。由某些存储引擎实现,比如InnoDB。
InnoDB的行级锁,按照锁定范围来说
分为四种:
1.记录锁(Record Locks):锁定索引中一条记录。
2.间隙锁(Gap Locks):要么锁住索引记录中间的值,要么锁住第一个索引记录前面的值或者最后一个索
引记录后面的值。
3.临键锁(Next-Key Locks):是索引记录上的记录锁和在索引记录之前的间隙锁的组合(间隙锁+记录
锁)。
4.插入意向锁(Insert Intention Locks):做insert操作时添加的对记录id的锁。
InnoDB的行级锁,按照功能来说
分为两种:
1.共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
2.排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。
InnoDB行锁是通过给索引上的索引项加锁来实现的,因此InnoDB这种行锁实现特点意味着:只有通过
索引条件检索的数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!
注意:插入意向锁
(1)插入意向锁是一种Gap锁,不是意向锁,在insert操作时产生。
(2)在多事务同时写入不同数据至同一索引间隙的时候,并不需要等待其他事务完成,不会发生锁等 待。
(3)假设有一个记录索引包含键值4和7,不同的事务分别插入5和6,每个事务都会产生一个加在4-7之 间的插入意向锁,获取在插入行上的排它锁,但是不会被互相锁住,因为数据行并不冲突。
(4)插入意向锁不会阻止任何锁,对于插入的记录会持有一个记录锁。