[MySQL] lock知识梳理
MySQL Lock机制
INDEX:
- MySQL事务隔离级别
- MVCC
- MySQL Lock类型
- MySQL MDL
CONTENT:
1. MySQL事务隔离级别
Read Uncommit
RU:
允许脏读, 也就是说本事务中可能读到其他事务中未提交的修改数据.
Read Commit
RC
只能读到已经提交的数据. Oracle等多数数据库默认都是该级别(不可重复读).
Repeatable Read
RR
可重复读, 在同一个事务内的查询都是事务开始时刻的一致性数据, InnoDB默认的事务隔离级别. 该隔离级别消除了不可重复读, 但是还存在幻想读.
Serializable
S
完全串行化的读, 每次读都需要获取表级别的共享锁, 读写相互组赛.
transaction-isolation = {READ-UNCOMMITTED | READ-COMMITTED | REPEATABLE-READ | SERIALIZABLE}
2. MVCC
MVCC: Multiversion concurrency control
基于锁的并发控制机制, 悲观机制;
基于版本的并发控制机制, 乐观机制;
读不阻塞写, 写也不阻塞读, 等到提交的时候才检验是否有冲突. 由于没有锁, 所以读写不会相互阻塞, 从而大大提升了并发性能.
3. MySQL Lock类型
根据锁的类型分:
- 共享锁
- 排他锁
- 意向共享锁
- 意向排他锁
根据锁的粒度分:
- 表锁
- 行锁
而在InnoDB存储引擎层实现了行锁(Record Lock), gap lock, next-key lock.
查看lock的信息
show variables like 'tx_isolation';
show engine innodb status\G
SELECT
r.trx_id waiting_trx_id, r.trx_query waiting_query, b.trx_id blocking_trx_id, b.trx_query blocking_query, b.trx_mysql_thread_id blocking_thread, b.trx_started, b.trx_wait_started
FROM
information_schema.innodb_lock_waits w
INNER JOIN information_schema.innodb_trx b
ON b.trx_id = w.blocking_trx_id
INNER JOIN information_schema.innodb_trx r
ON r.trx_id = w.requesting_trx_id;
MDL
先熟悉一下MDL_key的state name:
- global read lock
- commit lock
- schema metadata lock
- table metadata lock
- stored function metadata lock
- stored procedure metadata lock
- trigger metadata lock
- event metadata lock
从MySQL对锁持有的时间分:
Statement
语句执行期间获取的lock, 语句执行结束时自动释放的.
Transaction
在一个事务中, 此事务所设计到的所有表均要获取MDL, 一直到事务commit/rollback释放.
两阶段加锁协议:
事务执行分为两个阶段,
- 第一个阶段获得封锁;
- 第二个阶段释放封锁;
Explicit
需要MDL_context::release_lock(), 譬如: lock table; flush tables with read lock;
从MySQL的锁粒度分:
scope锁(MDL_scoped_lock(const MDL_key *key_arg))
名称 意义 IS 意向共享锁 IX 意向排他锁 S 共享锁 X 排他锁 优先级: X ->S ->IX (IS和其他类型的锁都是兼容的)
兼容性
individual locks转换为scoped lock: ----------------+-------------+ Type of request | Correspond. |
for indiv. lock | scoped lock |
----------------+-------------+
S, SH, SR, SW | IS |
SNW, SNRW, X | IX |
SNW, SNRW -> X | IX (*) | request与granted之间的兼容矩阵: | Type of active |
Request | scoped lock |
type | IS(**) IX S X |
---------+------------------+
IS | + + + + |
IX | + + - - |
S | + - + - |
X | + - - - | request与waiting兼容矩阵: | Pending |
Request | scoped lock |
type | IS(**) IX S X |
---------+-----------------+
IS | + + + + |
IX | + + - - |
S | + + + - |
X | + + + + | Here: "+" -- means that request can be satisfied
"-" -- means that request can't be satisfied and should wait (*) Since for upgradable locks we always take intention exclusive scoped
lock at the same time when obtaining the shared lock, there is no
need to obtain such lock during the upgrade itself.
(**) Since intention shared scoped locks are compatible with all other
type of locks we don't even have any accounting for them.
object锁(MDL_object_lock(const MDL_key *key_arg))
名称 意义 MDL_INTENTION_EXCLUSIVE 仅适用scoped locks, 可升级IX->X, 和其他IX兼容; 但是和scoped S and X不兼容. MDL_SHARED 共享锁. 访问字典对象(table metadata), 不能访问数据 MDL_SHARED_HIGH_PRIO 高优先级shared mdl, 不像shared lock那样, 在申请时会忽略X lock的等待; 用于访问metadata(no date), 填充INDORMATION_SCHEMA表. 兼容SNRW MDL_SHARED_READ 共享读锁. 能读table metadata, 也可读表数据(如select), 譬如: SELECTs, subqueries, and LOCK TABLE ... READ MDL_SHARED_WRITE 共享写锁. 读取metadata, modify/read table data, (INSERT, UPDATE, DELETE) statements,SELECT ... FOR UPDATE. but not LOCK TABLE ... WRITE or DDL) MDL_SHARED_UPGRADABLE alter table第一阶段获取. 读取metadata, modify/read table data(升级到SNW, 获取row_level lock), 可升级SHU->SNW/X. MDL_SHARED_NO_WRITE alter table第一阶段获取; 可以并发read table, 但不可以update; 可升级SNW->X. MDL_SHARED_NO_READ_WRITE 读取表metadata, modify/read table data. 但是阻止对表的读写操作. 用于LOCK TABLES WRITE statement. 可升级SNRW->X, 除S/SH外, 不兼容任何mdl. MDL_EXCLUSIVE 最高级别MDL锁, 通常用于Drop/Create/Rename等操作. 也用于其它对象创建或者删除时, 譬如: create trigger等 兼容性
request与granted兼容矩阵:(0为不可能情况) Request | Granted requests for lock |
type | S SH SR SW SNW SNRW X |
----------+------------------------------+
S | + + + + + + - |
SH | + + + + + + - |
SR | + + + + + - - |
SW | + + + + - - - |
SNW | + + + - - - - |
SNRW | + + - - - - - |
X | - - - - - - - |
SNW -> X | - - - 0 0 0 0 |
SNRW -> X | - - 0 0 0 0 0 | request与waiting兼容矩阵: Request | Pending requests for lock |
type | S SH SR SW SNW SNRW X |
----------+-----------------------------+
S | + + + + + + - |
SH | + + + + + + + |
SR | + + + + + - - |
SW | + + + + - - - |
SNW | + + + + + + - |
SNRW | + + + + + + - |
X | + + + + + + + |
SNW -> X | + + + + + + + |
SNRW -> X | + + + + + + + | Here: "+" -- means that request can be satisfied
"-" -- means that request can't be satisfied and should wait
"0" -- means impossible situation which will trigger assert @note In cases then current context already has "stronger" type
of lock on the object it will be automatically granted
thanks to usage of the MDL_context::find_ticket() method.
数据结构
和锁相关的数据结构:
- MDL_context: 字典锁上下文. 包含一个事物所有的字典锁请求.
- MDL_request: 字典锁请求. 包含对某个对象的某种锁的请求.
- MDL_ticket: 字典锁排队. MDL_request就是为了获取一个ticket.
- MDL_lock: 锁资源. 一个对象全局唯一, 可以允许多个可以并发的事物同时获得.
源码文件主要是sql/mdl.cc
加锁/解锁分析
SELECT加锁调用:
mysql_execute_command --> execute_sqlcom_select --> open_normal_and_derived_tables-->
open_tables --> open_and_process_table --> open_table--> MDL_context::acquire_lock
看看acquire lock的流程:
检查session是否持有该object的share lock? 如果是, 则grant.
if ((ticket= find_ticket(mdl_request, &found_duration)))
{
mdl_request->ticket= ticket;
}
如果找不到, 在MDL_map中找到MDL_lock, grant lock.
if (!(lock= mdl_locks.find_or_insert(key)))
{ }
ticket->m_lock= lock; if (lock->can_grant_lock(mdl_request->type, this))
{
lock->m_granted.add_ticket(ticket);
mysql_prlock_unlock(&lock->m_rwlock);
m_tickets[mdl_request->duration].push_front(ticket);
mdl_request->ticket= ticket; }
如果立即加锁失败, 则将ticket加入到lock的waiting队列中
lock= ticket->m_lock;
lock->m_waiting.add_ticket(ticket);
死锁检测
find_deadlock(); void MDL_context::find_deadlock()
{
while (1)
{
if (! visit_subgraph(&dvisitor))
{
breadk;
} victim= dvisitor.get_victim();
(void) victim->m_wait.set_status(MDL_wait::VICTIM);
victim->unlock_deadlock_victim(); if (victim == this)
break;
//发现deadlock, 函数退出
} }
SELECT解锁调用:
mysql_execute_command --> MDL_context::release_transactional_locks -->
MDL_context::release_locks_stored_before --> MDL_context::release_lock
看看release lock的流程:
通过ticket信息找到MDL_lock
DBUG_ASSERT(this == ticket->get_ctx());
mysql_mutex_assert_not_owner(&LOCK_open);
将ticket从MDL_lock的granted list中remove
lock->remove_ticket(&MDL_lock::m_granted, ticket);
void MDL_lock::Ticket_list::remove_ticket(MDL_ticket *ticket)
{
m_list.remove(ticket);
}
检查MDL_lock的waiting list和granted list是否为空? 如果是, 则将MDL_lock从MDL_map中remove
void MDL_map::remove(MDL_lock *lock) {
mysql_mutex_lock(&m_mutex);
my_hash_delete(&m_locks, (uchar*) lock); if (ref_usage == ref_release)
{
MDL_lock::destroy(lock);
}
} void MDL_context::destroy()
{
DBUG_ASSERT(m_tickets[MDL_STATEMENT].is_empty() &&
m_tickets[MDL_TRANSACTION].is_empty() &&
m_tickets[MDL_EXPLICIT].is_empty());
mysql_prlock_destroy(&m_LOCK_waiting_for); }
如果非空, 遍历waiting list, 尝试加锁, 并从waiting list中remove, 加入到granted list中, 唤醒对应session
SELECT与DDL(alter table)问题
经常会看到大SQL查询堵住alter table, alter table又会堵住后面的一堆SQL ......
看到一堆wait for table metadata lock, 醉了T.T
下来分析根本原因:
先看看alter的MDL流程:
opening tables阶段
MDL_INTENTION_EXCLUSIVE
MDL_SHARED_UPGRADABLE, 可升级到MDL_SHARED_NO_WRITE
copy data阶段
copy data to tmp table...
del原表, 将tmp表rename回去
MDL_SHARED_NO_WRITE升级到MDL_EXCLUSIVE
事务commit, release MDL
release MDL_INTENTION_EXCLUSIVE
release MDL_EXCLUSIVE
select是需要获取表的MDL_SHARED_READ
在alter table中的rename之前表被持有MDL_SHARED_NO_WRITE, 此时申请MDL_SHARED_READ是可以成功的, 兼容.
这里关键的步骤是rename环节, SNW->X; 如果此时表在被select(即被持有MDL_SHARED_READ); 从兼容矩阵可以看出SNW升级到X是不能与SR兼容的, 所以这个alter table的rename环节就必须等待select结束!
几种典型的加锁/释放锁的流程
select statement
opening tables阶段, 加共享读锁
- 加MDL_INTENTION_EXCLUSIVE锁
- 加MDL_SHARED_READ锁
commit阶段, 释放MDL锁
- 释放MDL_INTENTION_EXCLUSIVE锁
- 释放MDL_SHARED_READ锁
DML statement
opening tables阶段, 加共享写锁
- 加MDL_INTENTION_EXCLUSIVE锁
- 加MDL_SHARED_WRITE锁
commit阶段, 释放MDL锁
- 释放MDL_INTENTION_EXCLUSIVE锁
- 释放MDL_SHARED_WRITE锁
DDL(alter table) statement
opening tables阶段, 加共享写锁
- 加MDL_INTENTION_EXCLUSIVE锁
- 加MDL_SHARED_UPGRADABLE锁, 升级到MDL_SHARED_NO_WRITE
读取数据, copy data, 流程:
- create {tmp} table, 定义tmp表结构
- copy data from old table to tmp(new) table
tmp表替换老表(rename)
- 将MDL_SHARED_NO_WRITE升级到MDL_EXCLUSIVE
commit阶段, 释放MDL锁
- 释放MDL_INTENTION_EXCLUSIVE锁
- 释放MDL_EXCLUSIVE锁
[MySQL] lock知识梳理的更多相关文章
- MySQL 基础知识梳理
MySQL 的安装方式有多种,但是对于不同场景,会有最适合该场景的 MySQL 安装方式,下面就介绍一下 MySQL 常见的安装方法,包括 rpm 安装,yum 安装,通用二进制安装以及源码编译安装, ...
- MySQL 基础知识梳理学习(七)----sync_binlog
一般在生产环境中,很少用MySQL单实例来支撑业务,大部分的MySQL应用都是采用搭建集群的方法.搭建MySQL集群,可以进行数据库层面的读写分离.负载均衡或数据备份.基于MySQL原生的Replic ...
- MySQL 基础知识梳理学习(五)----详解MySQL两次写的设计及实现
一 . 两次写提出的背景或要解决的问题 两次写(InnoDB Double Write)是Innodb中很独特的一个功能点.因为Innodb中的日志是逻辑的,所谓逻辑就是比如插入一条记录时,它可能会在 ...
- MySQL 基础知识梳理学习(四)----GTID
在日常运维中,GTID带来的最方便的作用就是搭建和维护主从复制.GTID的主从模式代替了MySQL早期版本中利用二进制日志文件的名称和日志位置的做法,使用GTID使操作和维护都变得更加简洁和可高. 1 ...
- MySQL 基础知识梳理学习(一)----系统数据库
information_schema 此数据库是MySQL数据库自带的,主要存储数据库的元数据,保存了关于MySQL服务器维护的所有其他数据库的信息,如数据库名.数据库表.表列的数据类型及访问权限等. ...
- MySQL 基础知识梳理学习(六)----锁
1.什么是锁: 对共享资源进行并发访问控制,提供数据的完整性和一致性. 2.锁的区别: 类型 lock latch 对象 事务 线程 保护 数据库内容 内存数据结构 持续时间 整个事务过程 临界资源 ...
- MySQL 基础知识梳理学习(五)----半同步复制
1.半同步复制的特征 (1)从库会在连接到主库时告诉主库,它是不是配置了半同步. (2)如果半同步复制在主库端是开启了的,并且至少有一个半同步复制的从节点,那么此时主库的事务线程在提交时会被阻塞并等待 ...
- MySQL 基础知识梳理学习(三)----InnoDB日志相关的几个要点
1.InnoDB的特点 :(1)Fully ACID (InnoDB默认的Repeat Read隔离级别支持):(2)Row-level Locking(支持行锁):(3)Multi-version ...
- MySQL 基础知识梳理学习(二)----记录在页面层级的组织管理
1.InnoDB的数据存储结构 InnoDB中数据是通过段.簇.页面构成的. (1)段是表空间文件中的主要组织结构,它是一个逻辑概念,用来管理物理文件,是构成索引.表.回滚段的基本元素.创建一个索引( ...
随机推荐
- 【转】Mysql查询语句优化策略
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索 ...
- BFS:HDU-1072-Nightmare
Nightmare Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total ...
- OWINS是什么(转载)
OWIN的英文全称是Open Web Interface for .NET. 如果仅从名称上解析,可以得出这样的信息:OWIN是针对.NET平台的开放Web接口. 那Web接口是谁和谁之间的接口呢?是 ...
- mysql 分类
一.系统变量 说明:变量由系统提供,不用自定义 语法: 1.查看系统变量 show[global | session]varisables like ‘ ’:如果没有显示声明global 还是sess ...
- MSSQL将多行单列变一行一列并用指定分隔符分隔,模拟Mysql中的group_concat
-- 将多行记录(只能一个列)用指定分隔符分隔 IF(OBJECT_ID('sp_RowsChangeClosBySplit',N'P') IS NOT NULL) DROP PROC sp_Rows ...
- 【Jump Game II 】cpp
题目: Given an array of non-negative integers, you are initially positioned at the first index of the ...
- 【Sudoku Solver】cpp
题目: Write a program to solve a Sudoku puzzle by filling the empty cells. Empty cells are indicated b ...
- Wordpress 自定义文章类型添加 Categoried、Tags
默认情况下 ,自定义文章类型没有分类和标签属性,需要通过 register_taxonomy_for_object_type 手动注册文章分类和标签,可以通过在 functions.php 或插件中添 ...
- mysql环境变量配置(复制)
前面步骤完成后安装好MySQL,为MySQL配置环境变量.MySQL默认安装在C:\Program Files下. 1)新建MYSQL_HOME变量,并配置:C:\Program Files\MySQ ...
- js判断时间是否过期
var myDate=new Date(); myDate.setFullYear(2014,2,1); //2014年3月1日 //注意:表示月份的参数介于 0 到 11 之间.也就是说,如果希望把 ...