MySql

MySql锁机制

一、概述

相对其他数据库而言,MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制。
MySQL大致可归纳为以下3种锁:
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般

二、MySQL表级锁的锁模式(MyISAM)

MySQL表级锁有两种模式:表共享锁(Table Read Lock)和表独占写锁(Table Write Lock)。
对MyISAM的读操作,不会阻塞其他用户对同一表请求,但会阻塞对同一表的写请求;
对MyISAM的写操作,则会阻塞其他用户对同一表的读和写操作;
MyISAM表的读操作和写操作之间,以及写操作之间是串行的(当一线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作。其他线程的读
、写操作都会等待,直到锁被释放为止。)。
当一个线程获得对一个表的写锁后,只有持有锁线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。

MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此用户一般不需要直接用LOCK TABLE命令给MyISAM表显式加锁。

2.1、案例分析

手动增加表锁:
lock table 表名 read(write) ,表名2 read(write) ,...

mysql> lock table user read,tbl_emp write;
Query OK, 0 rows affected (0.02 sec)

查看表上加过的锁:
show open tables;

image
释放锁:
unlock tables;
image

例:
(1)、读锁:session1 对表user加读锁,自己可以查出user,session2也能查看表user,因为读锁为共享锁;session1不可以对表user进行写,也不可以对其他表进行读写。在session1未释放锁之前,session2对表user的写会阻塞。

(2)、写锁:当session1对表user加了写锁,session1在未释放写锁之前可以对改变进行读写,不可对其他表进行读写;session2在session1未释放user表的写锁之前,对阻塞session2对表user的操作。----写锁为独占锁。

2.2、案例结论:

MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此用户一般不需要直接用LOCK TABLE命令给MyISAM表显式加锁。
表共享读锁,表独占写锁;

锁类型      可否兼容                读锁                    写锁

读锁            是                  是                        否

写锁            是                  否                        否

结论:
(1)对MyISAM表的读操作(加读锁),不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;
(2)对MyISAM表的写操作(加写锁),则会阻塞其他用户对同一表的读和写请求;
(3)MyISAM表的读和写操作之间,以及写和写操作之间是串行的!(当一线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。)
简而言之:读锁会阻塞写,但是不会堵塞读;而写锁会把读和写都阻塞。

2.3、表锁锁分析

show status like 'table%'

image

这里有两个个状态变量记录Mysql内部表级锁定的情况,两个变量说明如下:
Table_locks_immediate:产生表级锁定的次数,表示可以立即获取锁的查询次数,每立即获取锁,值加1;
Table_locks_waited:出现表级锁定争用而发生等待的次数(不能立即获取锁的次数,每等待一次锁加一),此值高说明存在严重的表级锁争用的情况。

此外,Myisam的读写锁调度是写优先,这也是myisam不适合做写为主表的引擎。因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永久阻塞。

三、行锁(偏写)

3.1、复习

偏向InnoDB存储引擎,开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
InnoDB与MyISAM的最大不同有两点:
一是支持事务(TRANSACTION);
二是采用了行级锁;

1.事务(Transaction)及其ACID属性
事务是由一组SQL语句组成的逻辑处理单元,事务具有4属性,通常称为事务的ACID属性:

原性性(Actomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。

一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以操持完整性;事
务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。

隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。

持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。


2.并发事务带来的问题

 相对于串行处理来说,并发事务处理能大大增加数据库资源的利用率,提高数据库系统的事务吞吐量,从而可以支持更多的用户。但并发事务处理也
 会带来一些问题,主要包括以下几种情况:
    (1)更新丢失(Lost Update):当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发
    生丢失更新问题——最后的更新覆盖了其他事务所做的更新。例如,两个编辑人员制作了同一文档的电子副本。每个编辑人员独立地更改其副本,然
    后保存更改后的副本,这样就覆盖了原始文档。最后保存其更改副本的编辑人员覆盖另一个编辑人员所做的修改。如果在一个编辑人员
    完成并提交事务之前,另一个编辑人员不能访问同一文件,则可避免此问题
    (2)脏读(Dirty Reads):一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取
    同一条记录,如果不加控制,第二个事务读取了这些“脏”的数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫
    做“脏读”。
    (3)不可重复读(Non-Repeatable Reads):一个事务在读取某些已经发生了改变、或某些记录已经被删除了的数据!这种现象叫做“不可重复读”。一句话:事务A读取到了事务B已
    经提交的修改数据,不符合隔离性
    (4)幻读(Phantom Reads):一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。一句话:事务A读取到了事务B提交的新增数据,不符合隔离性。
    脏读是事务B里面修改了数据,幻读是事务B里面新增的数据。

3.事务隔离级别
在并发事务处理带来的问题中,“更新丢失”通常应该是完全避免的。但防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新
的数据加必要的锁来解决,因此,防止更新丢失应该是应用的责任。“脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库
提供一定的事务隔离机制来。

数据库实现事务隔离的方式,基本可以分为以下两种:
(1)、一种是在读取数据前,对其加锁,阻止其他事务对数据进行修改。

(2)、另一种是不用加任何锁,通过一定机制生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事
务级)的一致性读取。从用户的角度,好像是数据库可以提供同一数据的多个版本,因此,这种技术叫做数据多版本并发控制(MultiVersion 
Concurrency Control,简称MVCC或MCC),也经常称为多版本数据库。

数据库的事务隔离级别越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并
发”是矛盾的,同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关心数据并
发访问的能力。
为了解决“隔离”与“并发”的矛盾,ISO/ANSI SQL92定义了4个事务隔离级别,每个级别的隔离程度不同,允许出现的副作用也不同,应用可以根据自己业务逻辑要求,
通过选择不同的隔离级别来平衡"隔离"与"并发"的矛盾:
事务4种隔离级别比较:
隔离级别/读数据一致性               读数据一致性            脏读        不可重复读          幻读
及允许的并发副作用

未提交读(Read uncommitted)        最低级别,只能          是              是              是
                                    保证不读取物理
                                    上损坏的数据

读已提交                            语句级                  否              是              是

可重复读                            事务级                 否               否             是

可序列化                            最高级别,事务级        否              否              否

最后要说明的是:各具体数据库并不一定完全实现了上述4个隔离级别,例如,Oracle只提供Read committed和Serializable两个标准级别,另外还自己定义的Read only隔离级别:
SQL Server除支持上述ISO/ANSI SQL92定义的4个级别外,还支持一个叫做"快照"的隔离级别,但严格来说它是一个用MVCC实现的Serializable隔
离级别。MySQL支持全部4个隔离级别,但在具体实现时,有一些特点,比如在一些隔离级下是采用MVCC一致性读,但某些情况又不是。mysql默认使用可重复读

4、InnoDB的行锁模式及加锁方法
InnoDB实现了以下两种类型的行锁:
共享锁(s):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务取得相同的数据集共享读锁和排他写锁。

另外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。
意向共享锁(IS):事务打算给数据行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
意向排他锁(IX):事务打算给数据行加排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

3.2、案例分析:

1、场景说明:在MySQL中,事务由单独单元的一个或多个SQL语句组成。将session1和session2的自动提交事务关闭。
当在session1中执行更新表user某条记录a1,单不提交事务时,session1可以看到自己对表user的更新a1,而session2中无法看到session1对表user的更新a1,当session1执行commit提交事务之后,session2依然无法看到session1对user的更新a1,只有当session2对当前事务提交之后才能看到session1对表user的更新a1;
    当session1对表user中的某一数据行a进行写操作,单未提交事务,若session2对表user中的同一条数据行a执行写操作,将会被阻塞。

2、无索引,行锁升级为表锁
当一个表A的索引为varchar型,但是在写操作时由于某些原因使得varchar  未用 ' ',  导致系统自动转换类型, 行锁变表锁。

3.3、InnoDB的行锁模式及加锁方法

如果一个事务请求的锁模式与当前的锁兼容,InnoDB就请求的锁授予该事务;反之,如果两者两者不兼容,该事务就要等待锁释放。
意向锁是InnoDB自动加的,不需用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会任何锁;事务可以通过以下语句显示给记录集加共享锁或排锁。
共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE
用SELECT .. IN SHARE MODE获得共享锁,主要用在需要数据依存关系时确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或者DELETE操作
。但是如果当前事务也需要对该记录进行更新操作,则很有可能造成死锁,对于锁定行记录后需要进行更新操作的应用,应该使用SELECT ... FOR UPDATE方式获取排他锁。

3.4、获取InonoD行锁争用情况

可以通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况:show status like 'innodb_row_lock%';

image

Innodb_row_lock_current_waits:当前正在等待锁定的数量
Innodb_row_lock_time:从系统启动到现在锁定总时间长度
Innodb_row_lock_time_avg:每次等待所化的平均时间
Innodb_row_lock_waits:系统启动后到现在总共等待的次数。 

四、间隙锁(Next-Key锁)

当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据的索引项加锁;对于键值在条件范围内但并不
存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。

    举例来说,假如emp表中只有101条记录,其empid的值分别是1,2,...,100,101,下面的SQL:
        SELECT * FROM emp WHERE empid > 100 FOR UPDATE
    这是一个范围条件的检索,InnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的“间隙”加锁。

优点: InnoDB使用间隙锁的目的,一方面是为了防止幻读,以满足相关隔离级别的要求,对于上面的例子,要是不使用间隙锁,如果其他事务插入了empid大于100的任何记录,那么本事务如果再次执行上述语句,就会发生幻读;

危害:很显然,在使用范围条件检索并锁定记录时,InnoDB这种加锁机制会阻塞符合条件范围内键值的并发插入,这往往会造成严重的锁等待。因此,在实际开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件。
文章目录
  1. 1. 一、概述
  2. 2. 二、MySQL表级锁的锁模式(MyISAM)
    1. 2.0.1. 2.1、案例分析
    2. 2.0.2. 2.2、案例结论:
    3. 2.0.3. 2.3、表锁锁分析
  • 3. 三、行锁(偏写)
    1. 3.0.1. 3.1、复习
    2. 3.0.2. 3.2、案例分析:
    3. 3.0.3. 3.3、InnoDB的行锁模式及加锁方法
    4. 3.0.4. 3.4、获取InonoD行锁争用情况
  • 4. 四、间隙锁(Next-Key锁)