MySQL 锁机制

Posted 刘枫_Leo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL 锁机制相关的知识,希望对你有一定的参考价值。

锁概述

锁是计算机协调多个进程或线程并发访问某一资源的机制,MyISAM和MEMORY存储引擎采用的是表级锁(table-levellocking);InnoDB存储引擎既支持行级锁(row-levellocking),也支持表级锁,默认为行级锁

并发控制

在数据库中,数据是允许多个用户同时访问,因此在并发的场景下需要确保数据一致性。
大致分为三种场景

  1. 多用户同时读数据,不会引发数据不一致问题
    2.读 - 写 : 可能出现脏读幻读,不可重复读
    3.写 - 写 :并发更新同一行数据会导致更新丢失

并发控制技术

1. 悲观并发控制PCC :为保证数据安全采用“先取锁在访问”的策略,加锁会产生额外开销并增加死锁的机会
2. 乐观并发控制OCC :数据在处理时互不影响,只有在commit是检查是否存在其它食物修改了该数据,容易发生事务冲突
3. 多版本并发控制MVCC:每一个写操作都会创建数据副本,读操作根据可见性规则返回数据副本。可解决读-写 冲突。
4. MVCC+2PL:mysql中实现多版本两阶段锁协议,也就是MVCC+2PL(2PL是悲观并发实现的一种算法,锁只有在commit或rollback的时候释放)

锁分类

全局锁

全局锁是对整个数据库加锁,加锁之后
  ~数据库处于只读状态
  ~阻塞DML和DDL

加锁方式:lock flush tables with read lock;
解锁方式:unlock tables

全局锁主要作用在全库的逻辑备份,发生时会自动释放。
MyISAM、InnoDB都支持全局锁,但InnoDB一般不使用
基于InnoDB对事物的支持以及MVCC多版本并发的实现,InnoDB可以选择mysqldump工具加 –single-transaction参数,在不阻塞写操作的同时做全库的逻辑备份

表级锁

表级锁对整张表进行加锁,资源消耗少,不会出现死锁。
共享锁:阻塞写
独占锁:读写阻塞
MyISAM引擎默认支持表级别锁
表级别的锁有两种:表锁和元数据锁(MDL)

加锁方式:lock tables tb_name read/write
解锁:unlock table tb_name (连接中断也会自动释放)

元数据锁MDL

隐式锁,主要针对表结构改变的操作,没有显示的操作方式,访问表时自动加锁。

DML 加共享锁(读锁)
DDL 独占锁   (写锁)

MySQL在5.6之后引入online DDL,也就是进行DDL操作时MDL写锁会降级成读锁,线上DML操作不会被阻塞,DDL操作完成之后升级回MDL写锁然后释放
查看表级锁争用情况:SHOW STATUS LIKE ‘table%’

行级锁

InnoDB支持行级别锁,锁粒度小并发度高,但是加锁开销大也很可能会出现死锁,锁模式:

共享锁(读锁) S:对同一行的操作读不阻塞,阻塞写
排它锁(写锁) X:对同一行的操作读写都会阻塞
意向共享锁 IS:一个事物想要加S锁时必须先获得该表的IS
意向排它锁 IX:一个事物想要加X锁时必须先获得该表的IX

为什么需要意向锁:
意向锁是表级别的锁,用来标识该表上有数据被锁住或即将被锁,对于表级别的请求(LOCK TABLE…),就可以直接判断是否有锁冲突,不需要逐行检查锁的状态

InnoDB的默认隔离级别RR(可重复读),在RR下读数据有两种方式:

快照读:在MVCC下,事物开启执行第一个SELECT语句后会获取一个数据快照,直到事物结束读取到的数据都是一致的
    普通的 select… 查询都是快照读

当前读:读取的数据的最新版本,并且在读的时候不允许其它事物修改当前记录
    select… lock in share mode(读锁)
    select… for update(写锁)

加锁方式:
    普通 select… 查询 (不加锁)
    普通 insert、update、delete… (隐式加写锁)
    select…lock in share mode (加读锁)
    select…for update (加写锁)
解锁:
    提交/回滚事物(commit/rollback)
    kill 阻塞进程

innodb锁

innodb行锁是通过给索引上的索引项加锁来实现的。即使在建表的时候没有指定主键,innodb会创建一个默认的DB_ROW_ID的自增字段为表的主键,并且在其主键索引为GEN_CLUST_INDEX 聚簇索引

死锁

死锁指的是两个或两个以上的事物在执行过程中争抢锁资源而造成相互等待的情况表锁不会出现死锁,主要还是针对InooDB的行锁,可以看下面的例子:

锁发现并解决

# 查询InnoDB锁的整体情况
# 可以重点查看Innodb_row_lock_waits和Innodb_row_lock_time_avg这两个值
# 如果数值较大,说明锁之间的竞争大
show status like innodb_row_lock%;

#可以通过INNODB_TRX、INNODB_LOCKS、INNODB_LOCK_WAITS这三个表
#分析可能存在的锁的问题
select * from information_schema.INNODB_TRX; # 查看所有事物
select * from information_schema.INNODB_LOCKS; # 查看锁
select * from information_schema.INNODB_LOCK_WAITS; # 查看锁等待

解决:
超时等待,事物超时自动回滚(innodb_lock_wait_timeout 默认50s)
主动死锁检测,事物请求锁的时候采用 wait-for graph 等待图的方式进行死锁检测(innodb_deadlock_detect 默认on)
发现死锁也可以人为 kill 进程

以上是关于MySQL 锁机制的主要内容,如果未能解决你的问题,请参考以下文章

Mysql各种锁机制

MySQL锁机制

MySQL锁机制

MySQL锁机制

mysql插入意向锁测试

mysql innodb插入意向锁