MySQL中的锁与MVCC

1. MySQL中的锁

MySQL存储引擎:MyISAM、InnoDB、MERGE、MEMORY(HEAP)、BDB(BerkeleyDB)、EXAMPLE、FEDERATED、ARCHIVE、CSV、BLACKHOLE。此处,着重InnoDB 与 MyISAM。

表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。

1.1 MyISAM

MyISAM存储引擎的表锁可分为:表读锁(共享锁)表写锁(独占锁),读锁和写锁是互斥的,读写操作是串行的。

MyISAM表的读写操作特点

Q: 一个进程请求某个 MyISAM表的读锁,同时另一个进程也请求同一表的写锁,MySQL如何处理呢?

A: 写进程先获得锁。

**本质上,MySQL中写请求优先级高于读请求。因此,即使读请求先到锁等待队列,写请求后到,写请求也会插到读请求前。**

MyISAM表不适用有大量更新操作和查询操作的场景,其原因正是:

**大量的更新操作会造成查询操作很难获得读锁,甚至可能长时间持续阻塞。**配合锁调度能有效减缓此现象。

MyISAM的锁调度

  • 通过指定启动参数 low-priority-updates,使MyISAM引擎默认给予读请求以优先的权利。
  • 通过执行命令SET LOW_PRIORITY_UPDATES=1,使该连接发出的更新请求优先级降低。
  • 通过指定INSERT、UPDATE、DELETE语句的LOW_PRIORITY属性,降低该语句的优先级。

1.2 InnoDB

之前在MySQL索引与执行计划中已经提及InnoDB与MyISAM从底层到上层的不同,针对锁相关,主要不同之处为:

  • 事务:InnoDB 支持事务; MyISAM不支持事务;
  • 锁:InnoDB 提供表锁、行锁; MyISAM提供表锁; InnoDB的行锁实现在索引上,行锁过程会先锁住二级索引再锁住主键索引,而非锁在物理行记录。如果访问没有命中索引,也无法使用行锁,将要退化为表锁。
  • 读写性能:InnoDB 适合频繁更新插入,并发大; MyISAM适合频繁查询与插入,更新较少;

InnoDB的行锁实现在索引上,行锁过程会先锁住二级索引再锁住主键索引,而非锁在物理行记录。如果访问没有命中索引,也无法使用行锁,将要退化为表锁。在并发情况下查询条件列未加索引而退化为表锁,明显会出现阻塞。

1.2.1 事务及其ACID属性

事务是由一组SQL语句组成的逻辑处理单元,事务具有4属性,通常称为事务的ACID属性。

1.2.2 并发事务带来的问题

相较于串行处理,并发事务处理能大大增加数据库资源的利用率,提高数据库系统的事务吞吐量,从而可以支持更多的用户。

但并发事务处理也会带来一些问题,主要包括以下几种情况。

1.2.3 事务隔离级别

解决了更新丢失,但还是可能会出现脏读

解决了更新丢失和脏读问题

解决了更新丢失、脏读、不可重复读、但是还会出现幻读

1.2.4 InnoDB中的锁归档

InnoDB中的锁归档划分如图所示。重点关注行锁类别:记录锁、间隙锁、临键锁、插入意向锁。

共享锁与排他锁

注意,存在表级的S锁、X锁(如前文MyISAM的表锁),也有行级别的S锁、X锁;

行锁

表级锁

1.3 死锁

不同事务间相互持有对方需要的锁资源,导致事务相互都处于等待对方释放资源状态,都无法进行下去,也不会主动释放其持有的锁资源。事务间资源依赖循环导致的事务无限等待状态。

1.3.1 死锁解决策略

1.3.2 避免死锁的方式

2. MVCC

MVCC(Multi-Version Concurrency Control,多版本并发控制)一种并发控制机制,在数据库中用快照多版本保存的形式,允许同一数据拥有不同版本,在查询时添加查询约束并获取对应版本数据,以此来控制并发执行的事务,达到控制事务隔离。

  • 每行数据都存在一个版本,数据更新时需要更新该版本;
  • 修改数据时Copy出当前版本进行随意修改,事务间隔离不可见;
  • 保存数据时进行版本号比较操作,若成功(commit),则覆盖原记录;失败则放弃copy,事务回滚;

MVCC 核心思想为保存数据在某个时间点的快照。不同存储引擎的MVCC,实现是不尽相同,典型分类有乐观并发控制和悲观并发控制。MySQL 中 InnoDB 引擎是支持 MVCC的,在应对高并发事务,MVCC 比单纯的加行锁更有效,开销更小。

2.1 InnoDB下的MVCC组成

InnoDB下的MVCC核心组成为undo logread_view 一致性视图

2.2 当前读 与 快照读

2.3 InnoDB下的MVCC策略

InnoDB 中 MVCC 的实现方式为:每一行记录都有两个隐藏列:DATA_TRX_ID、DATA_ROLL_PTR(如果没有主键,则还会多一个隐藏的主键列)。

Q: MVCC解决脏读问题

A:在RC隔离级别下,MVCC机制下每次查询都会创建一个 read_view 一致性视图 ,做好版本可见性控制,使事务只能看到已提交数据,从而避免看见未提交数据,解决脏读。

Q: MVCC解决不可重复读问题

A: 在RC隔离级别下 ,MVCC机制下每次查询都会创建一个 read_view 一致性视图 ,对于已提交的事务,再次进行重复读会创建一个新的 read_view 一致性视图,因此,还是可能会造成读取过程的不可重复读。在RR隔离级别下 ,事务从启动到结束只创建一个新的 read_view 一致性视图,因此不会出现不可重复读。

Q: MVCC解决幻读问题

A: 当前读 与 快照读 分情况论述,MVCC只能解决快照读的幻读,只有临键锁(Next-key Locks)加持才能解决当前读的幻读。

快照读:通过MVCC来进行控制的,不用加锁。按照MVCC中规定的“语法”进行增删改查等操作,以避免幻读。

当前读:通过临键锁(Next-key Locks)来避免幻读。

最后,推荐阅读