【MySQL系列】03.mysql锁&事务ACID及实现原理

 

一、MySQL锁机制

1、锁的概述

在计算机中,锁是一种协调多个进程或线程并发访问某一资源的机制。在数据库中,除了系统的计算资源(如CPU,内存,IO等)存在争夺外,存储的数据资源很多用户也是可以共享的。怎么保证数据并发访问的一致性和有效性,是所有数据库必须面临的一个问题,而锁的冲突也是影响数据库并发访问性能的一个重要因素,这点需要格外关注。

2、MySQL数据库中的锁

MySQL数据库中存在两种锁,即Latch和Lock,它们都是锁,但具有不同的含义。Latch锁,又叫闩锁,是一种轻量级锁,要求锁定的时间必须非常短,否则将严重影响应用性能;在InnoDB存储引擎中,Latch又可以分为mutex(互斥量)和rwlock(读写锁),作用是保证并发线程操作临界资源的正确性,通常没有死锁检测的机制。Lock锁,用来锁定数据中的对象(如表/页/行等),lock的对象就是事务,通常lock对象是在事务commit或者rollback后释放,而不同事务隔离级别释放的时间可能不同。后面重点说明Lock锁。

①.锁的类型

对数据而言,操作方式只有读和写,数据库在实现锁的时候,对这两种方式使用了不同的锁。以InnoDB引擎为例,它实现了标准的行级锁 —— 即共享锁(Shared Lock)和互斥锁(Exclusive Lock)。

  • 共享锁(读锁):允许事务读一行数据,共享锁之间是兼容的。
  • 排他锁(写锁):允许事务删除或更新一行数据,排他锁与其他任意锁都不兼容,互斥。
兼容性分析 S锁 X锁
S锁 ×
X锁 × ×

其实,这样的设计也很好理解,共享锁代表了读操作、排他锁代表了写操作,我们可以在数据库中并行读,但是只能串行写,只有这样才能保证不会发生线程竞争,实现线程安全

②.锁的粒度

MySQL数据库中Lock锁的粒度有表锁、页锁、行锁,不同的存储引擎有着不同粒度的锁,如图所示:

不同存储引擎对应的所粒度 表锁 页锁 行锁
InnoDB ×
MyISAM × ×
BerkeleyDB ×

A、表锁

关于表级锁的描述如下:

  • 是MySQL各存储引擎中最大颗粒度的锁定机制,因此实现逻辑非常简单,带来的系统负面影响最小,获取锁和释放锁的速度很快;
  • 由于表级锁一次会将整个表锁定,可以很好的避免困扰我们的死锁问题,但资源的争夺概率会增大,并发度较低;
  • 使用表级锁定的主要是MyISAM,MEMORY,CSV等一些非事务性存储引擎;

使用表锁的语法:

# 获取表锁
LOCK TABLES
    tbl_name [[AS] alias] lock_type
    [, tbl_name [[AS] alias] lock_type] ...

# 锁类型:读和写
lock_type:
    READ [LOCAL]  | [LOW_PRIORITY] WRITE

# 释放表锁
UNLOCK TABLES

# 以MyISAM举例,在执行查询前,会自动执行表的加锁、解锁操作,一般情况下不需要用户手动加、解锁。
# 但是有的时候也需要显示加锁。比如:检索某一个时刻t1,t2表中数据数量
LOCK TABLE t1 read, t2 read;
select count(t1.id1) as 'sum' from t1;
select count(t2.id1) as 'sum' from t2;
UNLOCK TABLES;

B、页锁

关于页级锁的描述如下:

  • 锁定颗粒度介于行级锁定与表级锁之间,所以获取锁定所需要的资源开销,以及所能提供的并发处理能力也同样是介于二者之间;
  • 随着锁定资源颗粒度的减小,锁定相同数据量的数据所需要消耗的内存数量是越来越多的,实现算法也会越来越复杂,但应用程序的访问请求遇到锁等待的可能性也会随之降低,系统整体并发度也随之提升;
  • 使用页级锁定的主要是BerkeleyDB存储引擎,注意页级锁定和行级锁定一样,会发生死锁。

C、行锁

关于行级锁的描述如下:

  • 它的锁定颗粒度是最小的,发生锁定资源争用的概率也最小,能够给予应用程序尽可能大的并发处理能力而提高一些需要高并发应用系统的整体性能;
  • 虽然并发度较高,但每次获取锁和释放锁需要做的事情也更多,带来的消耗自然也就更大了,而且也最容易发生死锁;
  • 使用行级锁定的主要是InnoDB存储引擎。

下面通过表格整理下三者之间的异同点:

  锁粒度 加锁速度 锁冲突概率 并发度 是否死锁
表级锁 最大 最高 最低 不会出现
页级锁 介于之间 介于之间 介于之间 介于之间 会出现
行级锁 最小 最低 最高 会出现

D、总结

从锁的角度来说,表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用;而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统。

3、InnoDB中的锁

①.意向锁

InnoDB为了支持多粒度的锁(即表锁和行锁),引入了意向锁(Intention Lock)的概念。想象一下:如果没有意向锁,当已经有人使用行锁对表中的某一行进行修改时,如果另外一个请求要对全表进行修改,那么就需要对所有的行是否被锁定进行扫描,在这种情况下,效率是非常低的;不过,在引入意向锁之后,当有人使用行锁对表中的某一行进行修改之前,会先为表添加意向互斥锁(IX),再为行记录添加互斥锁(X),在这时如果有人尝试对全表进行修改就不需要判断表中的每一行数据是否被加锁了,只需要通过等待意向互斥锁被释放就可以了。

意向锁的类型分为两种:

  • 意向共享锁(IS):事务想要在获得表中某些记录的共享锁,需要在表上先加意向共享锁。
  • 意向互斥锁(IX):事务想要在获得表中某些记录的互斥锁,需要在表上先加意向互斥锁。

意向锁的加入将锁类型之间的兼容问题变得越来越复杂,而意向锁其实不会阻塞全表扫描之外的任何请求,它们的主要目的是为了表示是否有人请求锁定表中的某一行数据。兼容性对比如下表所示:

  IS IX S X
IS ×
IX × ×
S × ×
X × × × ×

②.行锁算法

InnoDB中有以下三种行锁的算法:

  • Record Lock:单个行记录上的锁。它总是会去锁住索引记录,如果InnoDB表在建立的时候没有设置任何一个索引,那么这时该引擎会使用隐式的主键来进行锁定。
  • Gap Lock:间隙锁,锁定一个范围,但不包含记录本身。
  • Next-Key Lock:即Gap Lock+Record Lock的组合,锁定一个范围,并且锁定记录本身。相对而言,也存在Previous-Key Lock,了解即可。

看个例子,有一个索引有四个值:10,11,13,20,那么该索引可能被Next-Key Locking的区间为:

还是上面索引的值,使用Previous-Key Locking技术,则可锁定的区间为:

并不是所有索引都会加上Next-key Lock的,在查询的列是唯一索引(包含主键索引)的情况下,Next-key Lock会降级为Record Lock,再来看个例子:有张表存在主键索引a,普通索引b,插入的数值分别为:a->1,3,5,7,9  b->1,1,3,6,8。

此时在会话A中执行 SELECT * FROM z WHERE b = 3 FOR UPDATE ,索引锁定如下:

那么,此时会话B执行的语句落在锁定范围内的(例如:SELECT * FROM z WHERE a = 5 LOCK IN SHARE MODE;)都会进行等待状态。因此,Gap Lock的作用是为了阻止多个事务将记录插入到同一个范围内,设计它的目的是用来解决Phontom Problem(幻读问题)。在MySQL默认的隔离级别(Repeatable Read)下,InnoDB就是使用它来解决幻读问题

幻读:是指在同一事务下,连续执行两次同样的SQL语句可能导致不同的结果,第二次的SQL可能会返回之前不存在的行,也就是第一次执行和第二次执行期间有其他事务往里插入了新的行。

当然,我们可以通过以下方式来显示的关闭Gap Lock:

  • 将事务的隔离级别设为 READ COMMITED;
  • 将参数innodb_locks_unsafe_for_binlog设置为1。

③.一致性非锁定读

④.一致性锁定读

在默认隔离级别RR下,InnoDB的select查询操作使用一致性非锁定读。但某些情况下,我们需要显式地对数据库读取操作进行加锁以保证数据逻辑的一致性,所以InnoDB对于select查询语句支持两种一致性的锁定读(locking read)操作:

SELECT … FOR UPDATE (X锁)
SELECT … LOCK IN SHARE MODE (S锁)

4、锁引发的问题

5、死锁问题

二、MySQL事务

1、事务概述

2、Redo log(重做日志)

①.Redo log概述

②.对比binlog

③.redo log block

④.crash recovery

3、Undo log

①.Undo log概述

②.purge

4、MVCC(多版本并发控制)

①.InnoDB存储引擎的行结构

②.事务链表

③.ReadView

④.RC和RR隔离级别ReadView的实现方式

5、常见问题小结

猜你喜欢

转载自blog.csdn.net/qq_29119581/article/details/114814536
今日推荐