浅谈数据库隔离级别和实现方式

1.数据库事务并发出现的问题

读脏数据:一个事务读取了其他事务尚未提交的数据,当其他事务回滚后,读的数据就是错误的数据。

不可重复读:指一个事务对于某一值先后读取结果不一致。比如,当两次读取中间,其他事务对值进行了修改。

幻读:指当前查询的结果,无法支撑后续的业务需求。比如,我想插入一条数据,先去select该数据是否存在,发现不存在,对该条记录进行插入,结果发现无法插入,这种情况称为幻读。网上有很多人解释幻读是两次查询结果不一致,这种说法相对不准确。

丢失更新:一个线程对值的更新被另一个线程的更新覆盖。比如,两个事务同时获取值100,第一个线程把值+30然后提交,值成为130;另一个事务将其*2,值成为200提交。最后结果是200,而第一个线程所做的更新丢失掉了。

2.事务隔离级别

文所说的 MySQL 事务都是指在 InnoDB 引擎下,MyISAM 引擎是不支持事务的。在MySQL中,默认的隔离级别是REPEATABLE-READ

隔离级别脏读不可重复读幻读
读未提交(read uncommitted)可能可能可能
读提交(read committed)不可能可能可能
可重复读(repeatable read)不可能不可能可能
串行化(serialzable)不可能不可能不可能

读未提交:一个事务可以读取到另一个事务未提交的修改。这会带来脏读、幻读、不可重复读问题。(基本没用)

读已提交:一个事务只能读取另一个事务已经提交的修改。其避免了脏读,但仍然存在不可重复读和幻读问题。

可重复读:同一个事务中多次读取相同的数据返回的结果是一样的。其避免了脏读和不可重复读问题,但幻读依然存在。

串行化:事务串行执行。避免了以上所有问题。

3. 锁

1) 锁的分类

  • Shared Locks(共享锁/S锁)

若事务T对数据对象A加上S锁,则事务T只能读A;其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这就保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。

  • Exclusive Locks(排它锁/X锁)

若事务T对数据对象A加上X锁,则只允许T读取和修改A,其它任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。它防止任何其它事务获取资源上的锁,直到在事务的末尾将资源上的原始锁释放为止。在更新操作(INSERT、UPDATE 或 DELETE)过程中始终应用排它锁。

注意:排他锁会阻止其它事务再对其锁定的数据加读或写的锁,但是不加锁的就没办法控制了。

  • Record Locks(行锁)

行锁,顾名思义,是加在索引行(对!是索引行!不是数据行!)上的锁。比如select * from user where id=1 and id=10 for update,就会在id=1id=10的索引行上加Record Lock。

  • Gap Locks(间隙锁)

间隙锁,它会锁住两个索引之间的区域。比如select * from user where id>1 and id<10 for update,就会在id为(1,10)的索引区间上加Gap Lock。

  • Next-Key Locks(间隙锁)

也叫间隙锁,它是Record Lock + Gap Lock形成的一个闭区间锁。比如select * from user where id>=1 and id<=10 for update,就会在id为[1,10]的索引闭区间上加Next-Key Lock。

这样组合起来就有,行级共享锁,表级共享锁,行级排它锁,表级排它锁。

2) 什么时候会加锁?

在数据库增删改查四种操作中,insert、delete和update都是会加排它锁(Exclusive Locks)的,而select只有显式声明才会加锁:

  • select: 即最常用的查询,是不加任何锁的
  • select ... lock in share mode: 会加共享锁(Shared Locks)
  • select ... for update: 会加排它锁

4. 实现方式

  • READ UNCOMMITTED

顾名思义,事务之间可以读取彼此未提交的数据。机智如你会记得,在前文有说到所有写操作都会加排它锁,那还怎么读未提交呢?

机智如你,前面我们介绍排它锁的时候,有这种说明: 排他锁会阻止其它事务再对其锁定的数据加读或写的锁,但是对不加锁的读就不起作用了。

READ UNCOMMITTED隔离级别下, 读不会加任何锁。而写会加排他锁,并到事务结束之后释放。

实例1:

查-写:查并没有阻止写,表明查肯定并没有加锁,要不写肯定就阻塞了。写很明显,会加排它锁的。

实例2: 写-写:阻塞,表明,写会加排它锁。

  • READ COMMITTED

顾名思义,事务之间可以读取彼此已提交的数据。

InnoDB在该隔离级别(READ COMMITTED)写数据时,使用排它锁, 读取数据不加锁而是使用了MVCC机制。

因此,在读已提交的级别下,都会通过MVCC获取当前数据的最新快照,不加任何锁,也无视任何锁(因为历史数据是构造出来的,身上不可能有锁)。

但是,该级别下还是遗留了不可重复读和幻读问题: MVCC版本的生成时机: 是每次select时。这就意味着,如果我们在事务A中执行多次的select,在每次select之间有其他事务更新了我们读取的数据并提交了,那就出现了不可重复读,即:重复读时,会出现数据不一致问题,后面我们会讲解超支现象,就是这种引起的。

  • REPEATABLE READ

READ COMMITTED级别不同的是MVCC版本的生成时机,即:一次事务中只在第一次select时生成版本,后续的查询都是在这个版本上进行,从而实现了可重复读

但是因为MVCC的快照只对读操作有效,对写操作无效,举例说明会更清晰一点: 事务A依次执行如下3条sql,事务B在语句1和2之间,插入10条age=20的记录,事务A就幻读了。

  1. 1. select count(1) from user where age=20;

  2. -- return 0: 当前没有age=20的

  3. 2. update user set name=test where age=20;

  4. -- Affects 10 rows: 因为事务B刚写入10条age=20的记录,而写操作是不受MVCC影响,能看到最新数据的,所以更新成功,而一旦操作成功,这些被操作的数据就会对当前事务可见

  5. 3. select count(1) from user where age=20;

  6. -- return 10: 出现幻读

REPEATABLE READ级别,可以防止大部分的幻读,但像前边举例读-写-读的情况,使用不加锁的select依然会幻读。

  • SERIALISABLE

大杀器,该级别下,会自动将所有普通select转化为select ... lock in share mode执行,即针对同一数据的所有读写都变成互斥的了,可靠性大大提高,并发性大大降低。

读-写,写-写均互斥。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值