Mysql-innodb事务、锁以及MVCC并发版本控制

本文深入探讨了数据库事务的四大特性:原子性、一致性、隔离性和持久性,并详细解析了并发事务可能引发的问题,如脏读、不可重复读和幻读。此外,还介绍了事务的隔离级别以及InnoDB存储引擎的锁机制,包括共享锁、排他锁和意向锁,以及行级锁的实现方式。最后,讲解了MVCC并发版本控制机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、事务

  1.1 事务的四大特性

  • 原子性(Atomicity):原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。
  • 一致性(Consistency):一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
  • 隔离性(Isolation): 隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
  • 持久性(Durability):持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

1.2 并发事务带来的问题

  • 脏读:指在一个事务处理过程里读取了另一个未提交的事务中的数据。比如用户A的账户里有500元,事物T1:将money-100=400,但是该事物还未提交。 事物T2:读取用户A的账户,查看到A的账户有400。然后事物1回滚,用户A的账户又变回了原来的500。 事物T2就产生了脏读。
  • 不可重复读:不可重复读是指A事务读取了B事务已经提交的更改数据。假设A在取款事务过程中查询账户余额,B往该账户转账100后,A再次查询账户余额,会发现两次读取账户的余额发生不一致。不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。
  • 幻读:A事务读取到了B事务的新增数据。A事务统计存款账户的总金额为10000,这时B事务新增了一个账户存款为100,A事务再次统计总金额10100(产生了幻读)。幻读与不可重复读的区别,幻读读取了其他事务新增的数据,不可重复读,读取了其他事务的更改(或者删除)数据。

 

1.3 事务的隔离级别

为了达到上述事务特性,数据库定义了几种不同的事务隔离级别:

  • READ_UNCOMMITTED(未提交读): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  • READ_COMMITTED(提交读): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  • REPEATABLE_READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生(Mysql innodb存储引擎通过一些特殊的处理,在该隔离级别解决了幻读问题)。
  • SERIALIZABLE(串行): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

这里需要注意的是:Mysql 默认采用的 REPEATABLE_READ隔离级别。

2、Innodb的锁

Innodb 与 MyISAM 最大的不同在于:一是支持事务,二是采用行级锁。

共享锁:又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改;
加锁释锁方式:
select * from procuct WHERE id=1 LOCK IN SHARE MODE;

排他锁:
又称为写锁,简称X锁,排他锁不能与其他锁并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的锁(共享锁、排他锁),只有获取了排他锁的事务才可以对数据行进行读取和修改,(其他事务要读取数据可来自于快照)。

意向共享锁(IS):表示事务准备给数据行加入共享锁,即一个数据行加共享锁前必须先取得该表的IS锁,意向共享锁之间是可以相互兼容的
意向排它锁(IX):表示事务准备给数据行加入排他锁,即一个数据行加排他锁前必须先取得该表的IX锁,意向排它锁之间是可以相互兼容的
意向锁(IS、IX)是InnoDB数据操作之前自动加的,不需要用户干预。


为什么需要意向锁?

假如事务A锁住表中的一行(写锁),事务B锁住整个表(写锁)。如果没有意向锁,事务A既然锁住了某一行,其他事务就不可能修改这一行。这与”事务B锁住整个表就能修改表中的任意一行“形成了冲突。所以,没有意向锁的时候,行锁与表锁共存就会存在问题!有了意向锁之后,事务A在申请行锁(写锁)之前,数据库会自动先给事务A申请表的意向排他锁。当事务B去申请表的写锁时就会失败,因为表上有意向排他锁之后事务B申请表的写锁时会被阻塞。

加锁方式:
delete / update / insert 默认加上X锁
SELECT * FROM table_name WHERE ... FOR UPDATE

InnoDB的行锁实现方式:

通过给索引上的索引项加锁来实现的,如果没有索引,InnoDB将对表中的所有记录加锁,实际效果跟表锁一样。

InnoDB的行锁分为三种情况:

  • Next-key locks:锁住记录+区间(左开右闭),当sql执行按照索引进行数据的检索时,查询条件为范围查找(between and、<、>等)并有数据命中则此时SQL语句加上的锁为Next-key locks,锁住索引的记录+区间(左开右闭)。如果有一张表,用sql “SELECT * FROM product WHERE id > 3 AND id < 6 FOR UPDATE;” 去查询,那么该条语句会锁住(5,7] 这之间的数据。 这时候向表中插入id为6的数据,是会被阻塞。

a08d86c683090da78db95fee97c3f4095f3.jpg

ea029cf99de04f16e508f013b1d40f3ef8f.jpg

  • Gap locks:锁住数据不存在的区间(左开右开),当sql执行按照索引进行数据的检索时,查询条件的数据不存在,这时SQL语句加上的锁即为Gap locks,锁住索引不存在的区间(左开右开)。当用“SELECT * FROM product WHERE id > 2 AND id < 4 FOR UPDATE;”去查询,查不到数据,此时会锁住(1,5)区间的数据。
  • Record locks:锁住具体的索引项,当sql执行按照唯一性(Primary key、Unique key)索引进行数据的检索时,查询条件等值匹配且查询的数据是存在,这时SQL语句加上的锁即为记录锁Record locks,锁住具体的索引项。当使用SELECT * FROM product WHERE id =2 FOR UPDATE 去查询,它会锁住id为2的这一行记录。

 

3、MVCC并发版本控制

MVCC就是同一条数据可以同时存在多个版本:更新数据时,先插入一条新记录,然后把旧记录标记为删除;查询时只查询事务开始前就已存在的记录。

  • 快照读:select语句默认,不加锁,MVCC实现可重复读,使用的是MVCC机制读取undo中的已经提交的数据。所以它的读取是非阻塞的。
  • 当前读:select语句加S锁或X锁;所有的修改操作加X锁。

RR隔离级别下的快照读,不是以begin开始的时间点作为snapshot建立时间点,而是以第一条select语句的时间点作为snapshot建立的时间点。

 

 

转载于:https://my.oschina.net/suzheworld/blog/3017404

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值