MySQL锁问题 - MyISAM与InnoDB的锁机制

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huangYao_long/article/details/83957950

一、MySQL锁概述

MySQL的锁机制相对于其他数据库而言比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制

1、表级锁和行级锁的特点

锁类型 开销 加锁速度 死锁 粒度 发生冲突概率 并发度
表级锁 最高 最低
行级锁 最小 最低 最高

2、表级锁和行级锁的适用场景

仅从锁的角度来讲:

锁类型 适合场景 实例
表级锁 以查询为主,只有少量按索引更新数据的应用 web
行级锁 有大量按索引条件并发更新少量不同数据,同时又有并发查询的用用 OLTP

二、MyISAM表锁

MyISAM只支持表锁

1、查询表级锁争用情况

可以通过检查table_locks_waitedtable_locks_immediate状态变量来分析系统上的表锁定争夺

show status like 'table%';

如果table_locks_waited的值比较高,说明存在比较严重的表锁争用情况

2、MySQL表级锁的锁模式

MySQL的表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)

锁模式/请求锁模式是否兼容 None 读锁 写锁
读锁
写锁

MyISAM表的读操作,不会阻塞其他读请求,但会阻塞写请求
MyISAM表的写操作,会阻塞其他的读写请求

MyISAM表的读、写操作之间,以及写操作之间是串行的,当一个线程获得对一个表的写锁后,其他线程的读写操作都会阻塞,直到锁释放

3、如何加表锁

MyISAM在执行SELECT语句迁,会自动给涉及的所有表加读锁
在执行UPDATE DELETE INSERT前,会自动给涉及的表加写锁
以上过程不需要用户干预,一般不需要直接用LOCK TABLE命令给MyISAM表显式加锁

MySQL不支持锁升级,执行LOCK TABLE后,只能访问显式加锁的表,不能访问未加锁的表,且读锁只能读不能写
自动加锁的情况下也是如此,MyISAM总是一次性获得SQL语句需要的全部锁,这也正式MyISAM不会出现死锁的原因

当使用LOCK TABLE时,不仅需要一次锁定用到的所有表,同一个表在SQL中出现多少次,都要按照别名锁定多少次

lock table post read;
select a.post_name,a.post_id,b.post_name,b.post_id from post a,post b where a.post_name = b.post_name and a.post_id = 1;
#ERROR 1100 (HY000):Table 'a' was not locked with LOCK TABLES
##需要对别名分别锁定
lock table post as a,post as b;

4、并发插入 Concurrent Inserts

总体而言,MyISAM表的读写操作是串行的;但在一定条件下MyISAM表也支持查询和插入操作的并发进行
通过修改MyISAM系统变量concurrent_insert的值(默认为1)来控制并发出入的行为

  • 0:不允许并发插入
  • 1:如果MyISAM表中没有空洞,在一个进程读的同时,允许另一个进程在表尾插入数据
  • 2:无论有没有空洞,都允许表尾并发插入数据

空洞:表的中间被删除的行

可以利用MyISAM的并发插入特性来解决对同一表查询和插入的锁争用问题

例如,将concurrent_insert的值设置为2,再定期在系统空闲时执行OPTIMIZE TABLE语句来整理空间碎片,收回因删除记录而产生的中间空洞

5、MyISAM的锁调度

MyISAM的读锁和写锁是互斥的,读写操作是串行的

那么当一个进程请求一个表的读锁,另一个进程请求同一表的写锁时如何调度呢?
答案是写进程先获得锁

不仅如此,即使在队列中读请求位于写请求前,写请求也会插队优先获得锁,这是因为MySQL认为写请求通常比读请求重要,这也是MyISAM表不太适合有大量更新操作和查询的原因,大量的查询会导致读请求持续阻塞在队列中,不过MyISAM支持通过一些设置来调节调度行为

  • 通过指定启动参数low-priority-updates使MyISAM引擎默认给予读请求以优先的权利
  • 通过执行命令SET LOW_PRIORITY_UPDATES=1使该链接发出的更新请求优先级降低
  • 通过指定INSERTUPDATEDELETE语句的LOW_PRIORITY属性,降低该类型语句的优先级
    通过以上3种方法,可以解决查询相对重要的应用中读锁严重阻塞的问题

MySQL也提供了一种这种的解决办法:
给系统参数max_write_lock_count设置一个合理的值,当写锁达到这个值后,MySQL会降低写请求的优先级

三、InnoDB锁问题

InnoDB和MyISAM最大的不同有两点:支持事务、支持行级锁

1、背景知识

事务及其ACID属性

  • 原子性 Atomicity:事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行
  • 一致性 Consistent:在事务开始和完成时,数据都必须保持一致性状态,即所有相关的数据规则必须应用于事务修改,以保持数据的完整性;事务结束时,所有的内部数据结构,如B树索引或双向链表,也都必须是正确的
  • 隔离性 Isolation:事务处理过程中对外界是不可见的,同时外部对事务的执行也没有影响
  • 持久性 Durable:事务结束后,对数据的修改是永久的

并发对事务处理的影响

并发事务处理可以大大增加资源利用率,提高数据库系统的事务吞吐量,但也会带来以下问题

  • 更新丢失:由于事务的隔离性,当两个事务需要处理同一行数据时,他们并没有感知,后提交的事务会覆盖之前的事务处理结果,解决方案是使这两个事务变成串行
  • 脏读:一个事务在对一条数据做修改的同时,另一个事务来读这条数据,这条数据就处于不一致状态,第二个事务读取了脏数据,并进一步处理,就会产生未提交的数据依赖关系,这种现象被称为脏读
  • 不可重复读:一个事务在读取某些数据后的一个时间点,再次读以前读过的数据,却发现数据已经改变或删除了
  • 幻读:前置条件同不可重复读,事务在以相同条件读曾经读过的数据时,发现其他事务插入了满足查询条件的新数据

事务的隔离级别

更新丢失的情况不止要靠数据库事务控制器解决,需要对数据加必要的锁来解决
脏读、不可重复读、幻读都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决

  • 在读数据前,对其加锁,阻止其他事务对数据进行修改
  • 不加锁,通过一定机制生成数据库在请求时间点的数据快照,并用这个快照来提供一定级别(语句级或事务级)的一致性读取,这种技术被称为MVCC或MCC(MutiVersion Concurrency Control),多版本数据库

数据库的事务隔离越严格,并发副作用越小,但由于隔离的实质是串行化,往往付出的代价也越大
为了解决并发与隔离的矛盾,ISO/ANSI SQL92定义了4个事务隔离级别

隔离界别 / 读一致性级别及副作用 读数据一致性 脏读 不可重复读 幻读
未提交读 最低级别,只能保证不读取物理上损坏的数据
已提交读 语句级
可重复读 事务级
可序列化 最高级别,事务级

2、获取InnoDB行锁争用情况

可通过检查InnoDB_row_lock状态变量来分析系统上的行锁争夺情况

show status like 'innodb_row_lock%';

通过查询information_schema数据库中的表了解锁等待情况

select * from innodb_locks\G

通过设置InnoDB Monitors观察锁冲突情况

CREATE TABLE innodb_monitor(a INT) ENGINE=INNODBshow engine innodb status\G

监视完成后需要及时关闭监视器,否则会导致.err文件变得非常巨大

DROP TABLE innodb_monitor;

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

InnoDB实现了以下两种类型的行锁

  • 共享锁 S:允许一个事务去读一行,阻止其他事务获取相同数据集的排它锁
  • 排它锁 X:只允许获得排它锁的数据更新数据,阻止其他的共享锁和排它锁

另外为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁,均为表锁,意向锁是InnoDB自动加的,不需要用户干预

UPDATE INSERT DELETE语句,自动加排它锁
普通的SELECT语句不会自动加锁

  • 意向共享锁 IS:事务打算加共享锁前,需要获得意向共享锁
  • 意向排它锁 IX:事务打算加排它锁前,需要获得意向排他锁
当前锁模式/是否兼容/请求锁模式 X IX S IS
X 冲突 冲突 冲突 冲突
IX 冲突 兼容 冲突 兼容
S 冲突 冲突 兼容 兼容
IS 冲突 兼容 兼容 兼容

如果一个事务请求的锁模式与当前锁模式兼容,InnoDB就将请求的锁授权予该事务;反之如果不兼容,就要等锁释放

事务可以通过以下语句显式给记录集加共享锁或排他锁

#共享锁
SELECT * FROM tb_name WHERE ... LOCK IN SHARE MODE;
#排他锁
SELECT * FROM TB_NAME WHERE ... FOR UPDATE;
#当前事务加了共享锁后再对数据更新,很有可能造成死锁,如果需要更新则应该加排他锁

4、InnoDB行锁实现方式

InnoDB行锁是通过给索引上的索引项加锁来实现的,如果没有索引,InnoDB将通过隐藏的聚簇索引来对记录加锁;InnoDB行锁分为3种情形

  • Record lock:对索引项加锁
  • Gap lock:对索引项之间的间隙、第一条记录前的间隙或最后一条记录后的间隙加锁
  • Next-key lock:前两种的组合,对记录和间隙加锁

InnoDB这种行锁实现特点意味着:如果不通过索引条件检索数据,就会锁表中的所有记录,实际效果和表锁一样

  • 在不通过索引查询时,InnoDB会锁定表中所有记录
  • 由于MySQL的行锁是针对索引加的锁,所以当访问不同行的记录,但使用相同的索引键,会出现锁冲突
  • 当表中有多个索引时,不同是事务可以使用不同的索引锁定不同的行,不论索引的类型,InnoDB都会使用行锁来锁定数据
  • 即便使用了索引字段,MySQL也可能认为扫全表效率更高,此时即会锁表,执行前可以查看执行计划

5、Next-Key锁

当使用范围条件查询时,InnoDB会对符合条件的数据加锁;对于范围内但不存在的记录,叫做间隙(GAP),InnoDB也会对这个间隙加锁,这就是Next-Key锁

InnoDB使用Next-Key锁的目的,一方面是为了防止幻读,另一方面是为了满足恢复和复制的需要

如果使用相等条件请求给一个不存在的记录加锁时,InnoDB也会使用Next-Key锁

6、恢复和复制的需要,对InnoDB锁机制的影响

7、InnoDB在不同隔离级别下的一致性读及锁的差异

8、什么时候使用表锁

9、关于死锁

四、总结

猜你喜欢

转载自blog.csdn.net/huangYao_long/article/details/83957950