Mysql高级

性能分析

explain

使用

explain select * from a where id=1;

列含义

(1)id列

id列的编号是select的序列号,有几个select就有几个id,并且id的顺序是按select出现的顺序增长的。id列值越大执行优先级越高,id相同则从上往下执行,id为NULL最后执行。

(2) select_type列

select_type表示对应行是简单还是复杂的查询
1) simple: 简单查询。查询不包含子查询和union
2) primary: 复杂查询中最外层的select
3) subquery: 包含在select中的子查询(不在from子句中)
4) derived:包含在from子句中的子查询。mysql会将结果存放在一个临时表中,也称为派生表
例子:
set session optimizer_switch=‘derived_merge=off’; #关闭mysql5.7新特性对衍生表的合并优化
EXPLAIN SELECT (select 1 from actor where id=1) from (select * from film where id=1) der;

5) union: 在union中的第二个和随后的select
EXPLAIN select 1 UNION ALL SELECT 1;

(3) table列

表示explain的一行正在访问哪个表
当from子句中有子查询时,table列是,表示当前查询依赖id=N的查询,于是先执行id=N的查询。当有union时,UNION RESULT的table列的值为 ,1和2表示参与union的select行id

(4) type列

这一列表示关联类型或访问类型,即Mysql决定如何查找表中的行,查找数据行记录的大概范围。依次从最优到最差分别为:system>const>eq_ref>ref>range>index>ALL
一般来说,得保证查询达到range级别,最好达到ref
NULL: msyql能够在优化阶段分解查询语句,在执行阶段用不着再访问表或索引。例如:在索引列中选取最小值,可以单独查找索引来完成,不需要在执行时访问表
explain select min(id) from film;

const,system: mysql能对查询的某部分进行优化并将其转化成一个常量(可以看show warnings的结果)。用于primary key或unique key的所有列与常数比较时,所有表最多有一个匹配行,读取一次,速度比较快。system是const的特例,表里只有一条元组匹配时为system

eq_ref: primary key 或unique key索引的所有部分被连接使用,最多只会返回一条符合条件的记录。这可能是在const之外最好的联接类型了,简单的select查询不会出现这种type
explain select * from film_actor left join film on film_actor.film_id = film.id;

ref: 相比eq_ref,不使用唯一索引,而是使用普通索引或者唯一性索引的部分前缀,索引要和某个值相比较,可能会找到多个符合条件的行
1) 简单select查询,name是普通索引(非唯一索引)
explain select * from film where name=‘1’

      2)关联表查询,idx_fime_actor_id和actor_id的联合索引,这里使用到了film_actor的左边前缀film_id部分
            explain select film_id from film left join film_actor on film.id= film_actor.film_id;

     range:范围扫描通常出现在in(),between,>,<,>=等操作中。使用一个索引来检索给定范围的行
      explain select * from actor where id>1         						    

index:扫描全表索引,这通常比ALL快一些。
说明:film表的所有字段都有索引
explain select *from film;

ALL:即全表扫描,意味着msyql需要从头到尾去查找所需要的行。通常情况下这需要增加索引进行优化了。
说明:actor表只有主键索引
explain select *from actor;

(5) possible_keys列

这一列显示查询可能使用哪些索引来查找
explain时可能出现possible_keys列有值,而key列显示null的情况,这种的情况是因为表中数据不多,mysql认为索引对此查询帮助不大,选择了全表查询。
如果该列是NULL,则没有相关的索引。在这种情况下,可以通过检查where子句看是否可以创造一个适当的索引来提高查询性能

(6) key列

这一列显示msyql实际采用了哪个索引来优化对该表的访问
如果没有使用索引,则该列是NULL。如果想强制mysql使用或者或是possible_keys列中的索引,在查询中使用force index、ignore index.

(7) key_len列

        这一列显示了mysql在索引了使用的字节数,通过这个值可以算出具体使用了索引中的哪些列
        key_len计算规则:http://www.xiaosongit.com/index/detail/id/655.html
       1)字符串
           char(n):n字节长度
           varchar(n):2字节存储字符串长度,如果是utf-8,则长度3n+2
       2)数值类型

tinyint: 1字节
smallint:2字节
int:4字节
bigint:8字节
3)时间类型
date:3字节
timestamp:4字节
datetime:8字节
4)如果字段允许为null,需要1字节记录是否为null
索引最大长度是768字节,当字符串过长时,mysql会做一个类似左前缀索引处理,将前半部分的字符提取出来做索引

(8) ref列

这个列显示了在key列记录的索引中,表查找值所有到的列或常量,常有的有:const(常量),字段名(如:film.id)

(9) rows列

这一列是mysql估计要读取并检测的行数,注意这个不是结果集里的行数

(10) Extra列

这一列显示的是额外信息。
常见的值有:
1) Using index:使用覆盖索引
我的理解:使用了索引并且查询到的值也在索引里
EXPLAIN select film_id from film_actor where film_id=2

2) Using where:使用where语句来处理结果,查询的列未被索引覆盖
我的理解:使用了where语句,并且查询没有使用到索引
explain select *from actor where name=‘1’;

3) Using index condition:查询的列不安全被索引覆盖,where 条件中是一个前导列的范围
explain select * from film_actor where film_id > 1;

      4)Using temporary: mysql需要创建一个临时表来处理查询。出现这种情况一般是要进行优化的,首先使用索引来优化
             例子:actor.name 没有索引 ,此时创建一张临时表来distinct

explain select distinct name from actor;

              优化: create index idx_name on actor(name);

      5)Using filesort:将用外部排序而不是索引排序,数据较小时从内存排序,否则需要从磁盘排序。这种情况一般也是需要索引进行优化

像order by排序 近排序字段需要有索引,查询的字段也需要索引。需要根据情况优化
例子:actor.name创建了索引,但是表中列有不带索引的
explain select * from actor order by name

explain select id from actor ORDER BY actor.name

        6)Select tables optimized away:是聚合函数如(min,max)来人访问存在索引中的某个字段。这种情况不需要优化,已经很棒了。

EXPLAIN select min(id) from actor
如果聚合函数中的字段没有存在索引中,可以给其加索引进行优化
7)Using MRR:使用MRR策略优化表数据读取,仅仅针对二级索引的范围扫描和 使用二级索引进行 join 的情况;MRR是多范围读取优化,MRR会将随机磁盘读,变成顺序磁盘读。一般是读取到的行数大于一定值时才会出现

MRR:通过磁盘扫描多范围读取(MRR)优化,MySQL尝试通过首先仅扫描索引并收集相关行的键来减少用于范围扫描的随机磁盘访问次数。然后对键进行排序,最后使用主键的顺序从基表中检索行。磁盘扫描MRR的动机是减少随机磁盘访问的次数,而是对基表数据进行更顺序的扫描。https://dev.mysql.com/doc/refman/8.0/en/mrr-optimization.html

变种

explain extended

会在explain的基础上额外提供一些查询优化的信息。紧随其后通过show warnings 命令可以得到优化后的查询语句,从而看出优化器优化了什么。额外还有filtered列,是一个半分比的值, rows*filtered/100 可以估算出将要和explain中前一个表进行连接的行数(前一个表指explain中的id指比当前表id值小的表)

explain partitions

相比explain多了个partitions字段,如果查询是基于分区表的话,会显示查询将访问的分区。

trace工具

开启和关闭

set session optimizer_trace="enabled=on",end_markers_in_json=on; --开启trace
set session optimizer_trace="enabled=off",end_markers_in_json=off; --关闭trace

使用

select * from employees where name > 'a' order by position;
SELECT * FROM information_schema.OPTIMIZER_TRACE;

注意事项

开启trace工具会影响mysql的性能,所以只能临时使用,使用完后就立马关闭

关键词

join_preparation:sql准备阶段
join_optimization:sql优化阶段
condition_processing:条件处理
table_dependencies:表依赖依赖情况
rows_estimation:预估访问成本
potential_range_indexes:查询可能使用的所有
analyzing_range_alternatives:分析各个索引的使用成本
“index_dives_for_eq_ranges”:true
“rowid_ordered”:false,‐‐使用该索引获取的记录是否按照主键排序
“using_mrr”:false,
“index_only”:false,‐‐是否使用覆盖索引
“rows”:5061,‐‐索引扫描行数
“cost”:6074.2,‐‐索引使用成本
“chosen”:false,‐‐是否选择该索
best_access_path:最终使用情况
“best_access_path”:{‐‐最优访问路径
“considered_access_paths”:[‐‐最终选择的访问路径
{
“rows_to_scan”:10123,
“access_type”:“scan”,‐‐访问类型:为scan,全表扫描
“resulting_rows”:10123,
“cost”:2052.6,
“chosen”:true,‐‐确定选择
“use_tmp_table”:true
}
]/* consid
refine_plan: 提纯后的执行计划,可以说是最终执行计划

在这里插入代码片

运维

编码操作

## 查看编码
show variables like 'char%';
## 修改编码  避免中文乱码
### 在[client]下追加:
default-character-set=utf8
### 在[mysql]下追加:
default-character-set=utf8
### 在[mysqld]下追加:
character-set-server=utf8

修改最大连接数

## 查看
 show VARIABLES like '%max_connections%'
## 临时设置
 set GLOBAL max_connections=1000
 ## 配置文件设置
 max_connections=1000

缓存池操作

  ## 查看innodb缓冲池大小
 show VARIABLES like 'innodb_buffer_pool_size';
 ## 查看innodb缓冲池实例个数
 show VARIABLES like 'innodb_buffer_pool_instances';
 ## 查看缓冲池实例信息
 select * from  information_schema.INNODB_BUFFER_POOL_STATS;
 ## 查看缓存池LRU列表
 select * from information_schema.INNODB_BUFFER_PAGE_LRU;

查看最近几十秒内innodb的运行情况

show engine innodb status;

查看正在运行的事务 INNODB_TRX

select * from information_schema.INNODB_TRX;

trx_id:事务ID。
trx_state:事务状态,有以下几种状态:RUNNING、LOCK WAIT、ROLLING BACK 和 COMMITTING。
trx_started:事务开始时间。
trx_requested_lock_id:等待事务的锁ID。如trx_state的状态为LOCK WAIT,那么该值代表当前的事务等待之前事务占用锁资源的ID。若trx_state不是LOCK WAIT,则该值为NULL,可以和 INNODB_LOCKS 表 JOIN 以得到更多详细信息。
trx_wait_started:事务开始等待的时间。
trx_weight:事务的权重。
trx_mysql_thread_id:事务线程 ID,可以和 PROCESSLIST 表 JOIN。
trx_query:事务正在执行的 SQL 语句。
trx_operation_state:事务当前操作状态。
trx_tables_in_use:当前事务执行的 SQL 中使用的表的个数。
trx_tables_locked:当前执行 SQL 的行锁数量。
trx_lock_structs:事务保留的锁数量。
trx_lock_memory_bytes:事务锁住的内存大小,单位为 BYTES。
trx_rows_locked:事务锁住的记录数。包含标记为 DELETED,并且已经保存到磁盘但对事务不可见的行。
trx_rows_modified:事务更改的行数。
trx_concurrency_tickets:事务并发票数。
trx_isolation_level:当前事务的隔离级别。
trx_unique_checks:是否打开唯一性检查的标识。
trx_foreign_key_checks:是否打开外键检查的标识。
trx_last_foreign_key_error:最后一次的外键错误信息。
trx_adaptive_hash_latched:自适应散列索引是否被当前事务锁住的标识。
trx_adaptive_hash_timeout:是否立刻放弃为自适应散列索引搜索 LATCH 的标识。

查看当前锁情况INNODB_LOCKS

select * from information_schema.INNODB_LOCKS

mysql8中为

select *  from performance_schema.INNODB_LOCKS

lock_id:锁 ID。
lock_trx_id:拥有锁的事务 ID。可以和 INNODB_TRX 表 JOIN 得到事务的详细信息。
lock_mode:锁的模式。有如下锁类型:行级锁包括:S、X、IS、IX,分别代表:共享锁、排它锁、意向共享锁、意向排它锁。表级锁包括:S_GAP、X_GAP、IS_GAP、IX_GAP 和 AUTO_INC,分别代表共享间隙锁、排它间隙锁、意向共享间隙锁、意向排它间隙锁和自动递增锁。
lock_type:锁的类型。RECORD 代表行级锁,TABLE 代表表级锁。
lock_table:被锁定的或者包含锁定记录的表的名称。
lock_index:锁住的索引,当 LOCK_TYPE=’RECORD’ 时,表示索引的名称;否则为 NULL。
lock_space:锁对象的space id .当 LOCK_TYPE=’RECORD’ 时,表示锁定行的表空间 ID;否则为 NULL。
lock_page:事务锁定页的数量。当 LOCK_TYPE=’RECORD’ 时,表示锁定行的页的数量;否则为 NULL。
lock_rec:锁定行的数量。当 LOCK_TYPE=’RECORD’ 时,表示一堆页面中锁定行的数量,亦即被锁定的记录号;否则为 NULL。
lock_data:锁定行的主键。当 LOCK_TYPE=’RECORD’ 时,表示锁定行的主键;否则为NULL。

查看当前锁等待情况INNODB_LOCK_WAITS

select * from information_schema.INNODB_LOCK_WAITS

requesting_trx_id: 申请锁资源的事务ID
requesting_lock_id:申请的锁的ID
blocking_trx_id:阻塞的事务ID
blocking_lock_id: 阻塞的锁的ID

关联查看事务和锁的信息

SELECT
	r.trx_id waiting_trx_id,
	r.trx_mysql_thread_id waiting_thread,
	r.trx_query wating_query,
	b.trx_id blocking_trx_id,
	b.trx_mysql_thread_id blocking_thread,
	b.trx_query blocking_query
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;
SELECT
	t.trx_state trx_state,
	t.trx_query trx_query,
	trx_tables_locked,
	l.lock_index lock_index,
	l.lock_page,
	l.lock_rec,
	l.lock_data lock_data 
FROM
	information_schema.INNODB_TRX t
	INNER JOIN information_schema.INNODB_LOCKS l;

官方文档

锁类型

共享锁和排他锁

innodb实现了两种标准的行级锁,共享锁(shared lock)和排他锁(exclusive lock)
共享锁(S锁):允许持有锁的事务去读一行数据
排它锁(X锁):允许持有锁的事务去删除或更新一行数据
如果一个事务T1已经获得行r的共享锁,那么事务T2可以立即获得行r的共享锁,因为读取并没有改变行r的数据,这种情况称为锁兼容
但如果事务T3想获得行r的排他锁,则必须等事务T1、T2释放行r上的共享锁,这种情况是锁不兼容

X S
X 不兼容 不兼容
S 不兼容 兼容

意向锁

概念

Innodb支持多粒度的锁,允许行锁和表锁共存。为了在多粒度的级别上实现锁,Innodb使用了意向锁。
意向锁是一个表级锁,它用来指示事务会在稍后需要对表中的行使用哪种类型的锁(s锁和L锁)。

类型:

意向共享锁(IS锁):表示事务意图在一个行上加共享锁
意向排它锁(IX锁):表示事务意图在一个行上加排他锁

SELECT … LOCK IN SHARE MODE; 该语句设置了IS锁也设置了S锁
SELECT … FOR UPDATE;该语句设置了IX锁也设置了X锁

意向锁协议

如果想获取一行的S锁,那么在这之前一定获取了IS锁或者更强的锁。
如果想获取一行的X锁,那么在这之前一定获取了IX锁。

锁之间的冲突情况

X IX S IS
X 冲突 冲突 冲突 冲突
IX 冲突 兼容 冲突 兼容
S 冲突 冲突 兼容 兼容
IS 冲突 兼容 兼容 兼容

意向锁在 show engine innodb status显示为

TABLE LOCK table `test`.`t` trx id 10080 lock mode IX

记录锁

概念

记录锁(record locks)是单个行记录上的锁。它总是会锁住索引记录,如果建立表的时候没有设置任何一个索引,则innodb引擎会使用隐式的主键进行锁定

记录锁在 show engine innodb status显示为

RECORD LOCKS space id 58 page no 3 n bits 72 index PRIMARY of table test.t
trx id 10078 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000a; asc ;;
1: len 6; hex 00000000274f; asc 'O;;
2: len 7; hex b60000019d0110; asc ;;这里是引用

间隙锁

概念

间隙锁(gap locks),是锁定一个范围,但不包含记录本身。
它锁定两个索引记录之间的间隙,或者是锁定第一个记录之前或者最后一个记录之后的间隙。
例如select c1 from t where c1 between 10 to 20 for update。其他事务不能向t.c1这一列插入10到20之间的值,无论该列是否已经有了这样的值。如会插入15到t.c1时会被阻止。因为范围内的所有值得间隙都被锁住了
间隙锁是权衡性能和并发的一个条件。

搜索唯一一行的情况

select * from t where id=10

如果id是唯一索引,搜索唯一一行的语句时不需要间隙锁定的
但是如果id没有索引或者有不唯一的索引,那么该语句将锁定前面的间隙。
这个就是Innodb删除和更新带上索引文章中唯一索引特性的一部分原因。

持有间隙锁情况

InnoDB中的Gap锁是“纯抑制的”,这意味着它们的唯一目的是防止其他事务插入Gap间隙锁可以共存。一个事务取得的间隙锁并不会阻止另一个事务取得同一间隙上的间隙锁。共享和排他间隔锁之间没有区别。它们彼此之间不冲突,并且执行相同的功能。
不同的事务可以在间隙上持有冲突的锁。如事务A可以在一个间隙上持有一个共享间隙锁(gap S-lock),而事务B可以在这个间隙上持有排他间隙锁(gap X-lock)。这样做的原因是,如果一条记录在索引中被清除,那么记录上不同事务的持有的间隙锁必须合并。

Next-Key Locks

next-key锁是索引记录上的记录锁和索引记录之前的间隙锁的结合。
Innodb执行行级锁的方式是:当它搜索或扫描一个表索引时,它会在扫描到的索引上加共享锁或者排他锁。因此,Innodb的行级锁实际行时索引记录锁
如果一个会话在一个索引中的记录R上有一个共享锁或者排他锁,另一个会话不能在这个记录R之前的间隙插入新的索引记录。
假如一个索引包含10、11、13和20,该索引可能的next-key锁覆盖以下区间

(∞,10]
(10,11]
(11,13]
(13,20]
(20,∞)

对于最后一个间隔,next-key 锁锁住索引最大值之上的间隙和suprenum伪记录。
suprenum不是一个真正的索引记录,该伪记录高于索引中任何的值。
默认情况下,InnoDB使用REPEATABLE READ事务隔离级别,在这种情况下,InnoDB使用next-key锁进行搜索和索引扫描,这样可以防止换行

在 SHOW ENGINE INNODB STATUS中展示

RECORD LOCKS space id 58 page no 3 n bits 72 index PRIMARY of table test.t
trx id 10080 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000a; asc ;;
1: len 6; hex 00000000274f; asc 'O;;
2: len 7; hex b60000019d0110; asc ;;

注意record lock会有一个but not gap

插入意向锁

概念

插入意向锁(insert intention lock)是一种意向锁,是有插入操作在插入行之前进行设置的。

工作原理

(1)如果多个事务插入到同一个索引间隙中,如果它们没有同时插入到间隙中的同一个位置,那么它们就不行要等待对方。如索引中有4和7的记录,现在要插入值分别为5和6的独立事务,在获得插入行的排他锁之前,每个事务都有插入意向锁锁住4和7之间的间隙,但是由于插入行不冲突,所以不会阻塞彼此。

mysql> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
mysql> INSERT INTO child (id) values (90),(102);

##事务一: 对ID大于100的索引记录设置排他锁
mysql> START TRANSACTION;
mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE;
+-----+
| id  |
+-----+
| 102 |
+-----+

##事务二:插入101

mysql> START TRANSACTION;
mysql> INSERT INTO child (id) VALUES (101);

查看SHOW ENGINE INNODB STATUS

RECORD LOCKS space id 31 page no 3 n bits 72 index `PRIMARY` of table `test`.`child`
trx id 8731 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 80000066; asc    f;;
 1: len 6; hex 000000002215; asc     " ;;
 2: len 7; hex 9000000172011c; asc     r  ;;...

(2)但是如果发生重复键错误,则会对重复键索引记录设置共享锁。如果有多个事务插入同一行(如果有一个事务已经获得排他锁),那么这种共享锁可能会导致死锁。如死锁案例1

自增锁

自增锁(auto-inc lock)是一种特殊的表级锁,由插入带有AUTO_INCREMENT的列的表中事务中获得
在最简单的情况下,如果有一个事务正在向表中插入值,那么任何其他对该表插入的事务都必须等待,以便由第一个事务插入的行接收连续的主键值

innodb_autoinc_lock_mode配置选项控制用于自增锁定的算法。它允许您选择如何在可预测的自动递增值序列和插入操作的最大并发性之间进行权衡。
自增锁章节

当AUTO_INCREMENT整数列的值用完时,后续INSERT 操作将返回重复键错误。这是一般的MySQL行为

事务模型

官方文档

概念

事务是一组原子性的SQL查询,是一个独立的工作单元,事务内的语句,要么全部执行成功,要么全部执行失败。
事务的ACID

  • 原子性(atomicity):一个事务必须被视为一个不可分割的最新工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚。
  • 一致性(consistency):数据库总是从一个一致性的状态转向另一个一致性的状态。
  • 隔离性(isolation):一个事务所做的修改在最终提交以前,对其他事务是不可见的
  • 持久性(durability):一旦事务提交,则其所做的修改就会永久的保存到数据库中

事务的隔离级别

Isolation是ACID中的I。innodb提供了以下四种隔离级别

未提交读 READ UNCOMMITTED

事务中的修改即使未提交,其他事务也可以读到。事务可以读取到未提交的数据就是脏读

提交读 READ COMMITTED

事务开始时可以看见已提交事务所做的修改。同一个事务中在多次读同样的记录可能会出现不同结果

可重复读 REPEATABLE READ

可重复读是Innodb默认的隔离级别。在同一次事务中多次读取同样记录结果是一致的,都是读取事务开始的第一次快照。这样就解决了脏读的问题。
对于锁定读语句(SELECT with For UPDATE或LOCK IN SHARE MODE)、UPDATE和DELETE语句,锁定取决于语句是使用具有唯一搜索条件的唯一索引,还是使用范围类型的搜索条件。

  • 对于具有唯一搜索条件的唯一索引,InnoDB只锁定找到的索引记录,而不锁定之前的空隙。
  • 对于其他搜索条件,InnoDB锁定扫描到的索引范围,使用间隙锁或next-key锁去阻塞其他事务插入到该范围覆盖的间隙中

可串行化 SERIALIZABLE

可串行化是最高的隔离级别,它通过强制事务串行执行,避免幻读。serializable会在读取的每一行数据上加锁,所以可能会导致大量的锁超时和锁竞争。实际应用中很少使用这个隔离级别,只有在非常需要确保数据一致性并且可以接受没有并发的情况下,才考虑此级别。
对于autocommit=0时,会对select添加隐藏添加共享锁SELECT ... LOCK IN SHARE MODE

自动提交 autocommit

autocommit=1
如果自动提交启动了,则每个事务都将相成自己的事务。如果该sql语句未返回错误,则成功返回结果后会自动提交。如果返回错误,则进行自动回滚。
如果在事务中,执行多条sql语句,则它们会在事务结束时统一进行提交或者回滚(启动它执行多语句事务 START TRANSACTION或 BEGIN 语句)
autocommit=0
如果关闭自动提交,那么会话总是有一个事务打开,commit和rollback结束当前事务,并开启一个新事务。如果会话没有明确提交事务或者回滚事务的情况下结束,mysql 则会回滚当前事务

一致性非锁定读

一致性的非锁定读是Innodb通过多版本控制的方式来读取数据某个时间点的快照数据。由于读取的是历史版本的数据不需要等待锁的释放,所以很大的提升了并发能力和其他会话可以自由地修改这些表。
一致性读是InnoDB进程在read COMMITTED和REPEATABLE read隔离级别查询语句时的默认模式

对于可重复读REPEATABLE READ

对于可重复读,同一事务中的所有一致读取将读取该事务开始时建立的快照。

对于读提交READ COMMITTED

对于读提交,事务中的每个一致性读都将设置并读取自己的最新快照。

一致性锁定读

在事务隔离级别为可重复读和读提交的,Innodb存储引擎的select操作使用一致性非锁定读,但在某些情况下,用户需要显示地对数据库读取操作进行加锁以保障数据逻辑的一致性。如经典的银行取钱存钱问题。mysql提供了两个一致性锁定读的操作:
select ... for update为查询加X锁
select... lock in share mode为查询加S锁

死锁

概念

死锁是指两个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。当多个事务试图以不同的顺序锁定资源时,就可能产生死锁。多个事务同时锁定同一个资源时,也会产生死锁。死锁只有在并发的情况才会发生。

创建表

CREATE TABLE t1 (i INT, PRIMARY KEY (i)) ENGINE = InnoDB;

AB-BA死锁

即A等待B,B等待A。以不同的顺序锁定资源,产生了相互等待产生死锁

事务A执行

START TRANSACTION;
SELECT * FROM t1 WHERE i = 1 for UPDATE;

事务B执行

START TRANSACTION;
SELECT * FROM t1 WHERE i = 2 for UPDATE;

事务A执行

START TRANSACTION;
SELECT * FROM t1 WHERE i = 2 for UPDATE;

事务B执行

START TRANSACTION;
SELECT * FROM t1 WHERE i = 1 for UPDATE;
1213 - Deadlock found when trying to get lock; try restarting transaction
时间: 0s

事务A和事务B都同时执行第一条更新语句,更新一条数据,同时也锁定了该行数据。接着每个事务都去尝试执行第二条更新语句,却发现该行已经被对方锁定了,然后两个事务都在等待对方释放锁,同时又持有对方的锁,则会陷入死循环,除非外力因素介入才能释放锁。这个外力因素就是mysql的死锁检测机制

S锁和X锁导致死锁 AS-BX-AX

事务A执行

START TRANSACTION;
SELECT * FROM t1 WHERE i = 1 LOCK IN SHARE MODE;

事务B执行

START TRANSACTION;
SELECT * FROM t1 WHERE i = 1 for UPDATE;

事务A执行

START TRANSACTION;
SELECT * FROM t1 WHERE i = 1 for UPDATE;

事务A先执行,使用S锁锁定一行数据,事务B执行,意图获取该行X锁,获得该行的锁请求,并等待事务A释放S锁。然后事务A又执行删除该行,由于事务B已经获得了该行的锁请求,所以事务A不能将锁升级成X锁,导致死锁

X锁和S锁导致的死锁 AX-B(范围锁)-AX(插入范围内)

当前事务持有了待插入记录的下一个记录的X锁,但是在等待队列中已经存在一个S锁(X锁)请求,这就会导致死锁

insert into t1 VALUES(1),(2),(4),(5)

事务A执行

START TRANSACTION;
select * from t1 where i=4 for UPDATE;

事务B执行

START TRANSACTION;
select * from t1 where i<=4 LOCK IN SHARE MODE;select * from t1 where i<=4 for UPDATE;

事务A执行

insert into t1 VALUES(3)   //死锁

事务A获取到一个X锁,事务B获得S锁请求,等待事务A释放X锁,然后事务A又要像3位置插入,由于事务A已经获得了-∝~4的锁请求,如果事务A像3位置插入成功的,那么事务B就得获得到3记录,显然是不合理的。

并发插入到同一间隙导入死锁

原理

插入意向锁原理:如果多个事务同时插入到同一个索引间隙,如果插入的是不同位置,则不需要等待对方。如果出现重复键(即相同的索引值会插入同一位置),则会为重复键设置共享锁。
即如果,多个事务插入同一间隙的同一个位置上(其中一个事务已经持有了排他锁),那么这种共享锁会导致死锁

场景一:多个会话同时插入一个索引位置

以主键为例,二级索引是相同原理

模拟并发
事务一执行

START TRANSACTION;
INSERT INTO t1 VALUES(1);

事务二执行

START TRANSACTION;
INSERT INTO t1 VALUES(1);

事务三执行

START TRANSACTION;
INSERT INTO t1 VALUES(1);

事务一执行

ROLLBACK;

由于事务一,获取到了排他锁,由于重复键错误,事务二和事务三请求该行的共享锁。事务一回滚后,事务一释放排他锁,并授予事务二和事务三排队的共享锁,由于两个事务都获得了该行的共享锁,导致两个事务都无法获取排他锁,mysql或主动将事务二和事务三其中一个设置成死锁,让另一个去执行成功。

场景二: 表中已经存在该值,其中一个事务删除,其他事务新增

模拟并发
事务一执行

START TRANSACTION;
DELETE FROM t1 WHERE i = 1;

事务二执行

START TRANSACTION;
INSERT INTO t1 VALUES(1);

事务三执行

START TRANSACTION;
INSERT INTO t1 VALUES(1);

事务一执行

COMMIT;

事务二提交成功,事务三死锁,原因同上

场景三,并发先删除后新增

事务一执行

START TRANSACTION;
DELETE FROM t1 WHERE i = 1;
INSERT INTO t1 VALUES(1);

事务二执行

START TRANSACTION;
DELETE FROM t1 WHERE i = 1;
INSERT INTO t1 VALUES(1);

事务三执行

START TRANSACTION;
DELETE FROM t1 WHERE i = 1;
INSERT INTO t1 VALUES(1);

事务一执行

COMMIT;

事务二提交成功,事务三死锁,原因同上。死锁案例一就是次场景。

注意:不是唯一索引的,这些操作更容易出现这种问题。特别是涉及到删除的情况下,所以建议高并发的情况下非唯一索引删除上最好要根据主键删除。

死锁案例

1、并发插入死锁问题(集合点并发100 先删除后插入导致死锁)

索引:zc_anonymous_expert:idx_tenantId_groupId
zc_expert_group_relation:idx_groupId

set autocommit=0;
BEGIN;
delete from zc_anonymous_expert where tenant_id='4040e48b6b9bc47a016b9bf58dbc0053' and group_id='4040e6f17801af02017801be69eb0148';
delete from zc_expert_group_relation where group_id='4040e6f17801af02017801be69eb0148';
insert into zc_anonymous_expert(uuid,create_ts,creator,tenant_id,name,show_order,judge_id,group_id) VALUES('d414a6e048ae4e1091980fe2eeeb2793','2021-04-13 19:08','4040e48b6b9bc47a016b9bf6d28b03da','4040e48b6b9bc47a016b9bf58dbc0053','专家1',1,'4040e6f17801af02017801be5edc0147','4040e6f17801af02017801be69eb0148'),('942e53dc81e24a50b9ffbb0677e65428','2021-04-13 19:08','4040e48b6b9bc47a016b9bf6d28b03da','4040e48b6b9bc47a016b9bf58dbc0053','专家2',2,'4040e6f17801af02017801be5edc0147','4040e6f17801af02017801be69eb0148'),('0a75b4ccd0ba4d11bcc20d7db5fb7be4','2021-04-13 19:08','4040e48b6b9bc47a016b9bf6d28b03da','4040e48b6b9bc47a016b9bf58dbc0053','专家3',3,'4040e6f17801af02017801be5edc0147','4040e6f17801af02017801be69eb0148'),('afcd889fae5845aebe170a06a0e381b3','2021-04-13 19:08','4040e48b6b9bc47a016b9bf6d28b03da','4040e48b6b9bc47a016b9bf58dbc0053','专家4',4,'4040e6f17801af02017801be5edc0147','4040e6f17801af02017801be69eb0148');
insert into zc_expert_group_relation(uuid,create_ts,creator,tenant_id,expert_id,group_id,show_order) VALUES('8f3e103965b646cfb3c89826344819a9','2021-04-13 19:08','4040e48b6b9bc47a016b9bf6d28b03da','4040e48b6b9bc47a016b9bf58dbc0053','ad3b0c1f9244400c8259faa49bedadc7','4040e6f17801af02017801be69eb0148',1),('8ac1025c636f47c1b88868a871992f1f','2021-04-13 19:08','4040e48b6b9bc47a016b9bf6d28b03da','4040e48b6b9bc47a016b9bf58dbc0053','939db35d1f7645b88ddd856d106aa5df','4040e6f17801af02017801be69eb0148',2),('320608d47e154eb19c35abe1d6228c9a','2021-04-13 19:08','8040e48b6b9bc47a016b9bf6d28b03da','4040e48b6b9bc47a016b9bf58dbc0053','e6e578aca1034e19af2f67574d31042f','4040e6f17801af02017801be69eb0148',3),('7269899facb84d3ba8b579af4d8984cd','2021-04-13 19:08','4040e48b6b9bc47a016b9bf6d28b03da','4040e48b6b9bc47a016b9bf58dbc0053','c8410386552541e08a450c97b3ec53ee','4040e6f17801af02017801be69eb0148',4);
commit;

查看show ENGINE INNODB STATUS

2021-04-13 19:08:12 0x2d0c
*** (1) TRANSACTION:
TRANSACTION 430914, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 11 lock struct(s), heap size 1136, 54 row lock(s), undo log entries 51
MySQL thread id 133, OS thread handle 15152, query id 37150 localhost 127.0.0.1 root update
insert into zc_expert_group_relation(uuid,create_ts,creator,tenant_id,expert_id,group_id,show_order) VALUES('7f3e103965b646cfb3c89826344819a9','2021-04-13 19:08','4040e48b6b9bc47a016b9bf6d28b03da','4040e48b6b9bc47a016b9bf58dbc0053','ad3b0c1f9244400c8259faa49bedadc7','4040e6f17801af02017801bfeb5b0161',1),('eac1025c636f47c1b88868a871992f1f','2021-04-13 19:08','4040e48b6b9bc47a016b9bf6d28b03da','4040e48b6b9bc47a016b9bf58dbc0053','939db35d1f7645b88ddd856d106aa5df','4040e6f17801af02017801bfeb5b0161',2),('320608d47e154eb19c35abe1d6228c9a','2021-04-13 19:08','4040e48b6b9bc47a016b9bf6d28b03da','4040e48b6b9bc47a016b9bf58dbc0053','e6e578aca1034e19af2f67574d31042f','4040e6f17801af02017801bfeb5b0161',3),('6269899facb84d3ba8b579af4d8984cd','2021-04-13 19:08','4040e48b6b9bc47a016b9bf6d28b03da','4040e48b6b9bc47a016b9bf58dbc0053','c8410386552541e08a450c97b3ec53ee','4040e6f17801af02017801bfeb5b0161',4),('03fb5baf2ea54612a2ce9c235a269b
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 2567 page no 17 n bits 184 index idx_groupId of table `p_eval`.`zc_expert_group_relation` trx id 430914 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

*** (2) TRANSACTION:
TRANSACTION 430915, ACTIVE 0 sec inserting, thread declared inside InnoDB 5000
mysql tables in use 1, locked 1
6 lock struct(s), heap size 1136, 3 row lock(s), undo log entries 1
MySQL thread id 125, OS thread handle 11532, query id 37152 localhost 127.0.0.1 root update
insert into zc_anonymous_expert(uuid,create_ts,creator,tenant_id,name,show_order,judge_id,group_id) VALUES('d414a6e048ae4e1091980fe2eeeb2793','2021-04-13 19:08','4040e48b6b9bc47a016b9bf6d28b03da','4040e48b6b9bc47a016b9bf58dbc0053','专家1',1,'4040e6f17801af02017801be5edc0147','4040e6f17801af02017801be69eb0148'),('942e53dc81e24a50b9ffbb0677e65428','2021-04-13 19:08','4040e48b6b9bc47a016b9bf6d28b03da','4040e48b6b9bc47a016b9bf58dbc0053','专家2',2,'4040e6f17801af02017801be5edc0147','4040e6f17801af02017801be69eb0148'),('0a75b4ccd0ba4d11bcc20d7db5fb7be4','2021-04-13 19:08','4040e48b6b9bc47a016b9bf6d28b03da','4040e48b6b9bc47a016b9bf58dbc0053','专家3',3,'4040e6f17801af02017801be5edc0147','4040e6f17801af02017801be69eb0148'),('afcd889fae5845aebe170a06a0e381b3','2021-04-13 19:08','4040e48b6b9bc47a016b9bf6d28b03da','4040e48b6b9bc47a016b9bf58dbc0053','专家4',4,'4040e6f17801af02017801be5edc0147','4040e6f17801af02017801be69eb
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 2567 page no 17 n bits 184 index idx_groupId of table `p_eval`.`zc_expert_group_relation` trx id 430915 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 2523 page no 11 n bits 168 index idx_tenantId_groupId of table `p_eval`.`zc_anonymous_expert` trx id 430915 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 83 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 30; hex 343034306534386236623962633437613031366239626635386462633030; asc 4040e48b6b9bc47a016b9bf58dbc00; (total 32 bytes);
 1: len 30; hex 343034306536663137383031616630323031373830316266656235623031; asc 4040e6f17801af02017801bfeb5b01; (total 32 bytes);
 2: len 30; hex 306538376635626666643863343664323862663364366165623130396261; asc 0e87f5bffd8c46d28bf3d6aeb109ba; (total 32 bytes);

*** WE ROLL BACK TRANSACTION (2)

分析

通过show ENGINE INNODB STATUS;分析,并发插入时事务(2)zc_expert_group_relation表锁住了space id 2567 page no 17 n bits 184 index idx_groupId这个间隙,而事务(1)也要往space id 2567 page no 17 n bits 184 index idx_groupId这个间隙插入,并发插入同一个间隙,有个持有锁,另一个等待锁,被mysql主动视为死锁

原理

https://dev.mysql.com/doc/refman/5.7/en/innodb-locks-set.html

INSERT sets an exclusive lock on the inserted row. This lock is an index-record lock, not a next-key lock (that is, there is no gap lock) and does not prevent other sessions from inserting into the gap before the inserted row.

Prior to inserting the row, a type of gap lock called an insert intention gap lock is set. This lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap. Suppose that there are index records with values of 4 and 7. Separate transactions that attempt to insert values of 5 and 6 each lock the gap between 4 and 7 with insert intention locks prior to obtaining the exclusive lock on the inserted row, but do not block each other because the rows are nonconflicting.

If a duplicate-key error occurs, a shared lock on the duplicate index record is set. This use of a shared lock can result in deadlock should there be multiple sessions trying to insert the same row if another session already has an exclusive lock.

有道翻译

INSERT对插入的行设置排他锁。这个锁是一个index-record锁,而不是next-key锁(也就是说,没有gap锁),并且不阻止其他会话在插入的行之前插入gap。
在插入行之前,设置了一种称为插入意图间隙锁的间隙锁类型。这个锁表示,如果多个事务插入到同一个索引间隙中,如果它们没有插入到这个间隙中的同一位置,那么它们就不需要等待对方。假设有值为4和7的索引记录。在获得插入行的排他锁之前,尝试分别插入值为5和6的独立事务用插入意图锁锁住4和7之间的间隙,但不会阻塞彼此,因为行是不冲突的。
如果发生重复键错误,则对重复索引记录设置共享锁。如果有多个会话试图插入同一行(如果另一个会话已经拥有排他锁),那么这种共享锁的使用可能会导致死锁。

解决方案

减小事务执行时间
方案一:删除单独一个事务,新增单独一个事务
方案二:删除先查出主键,在根据主键删除。删除和新增同一个事务

你可能感兴趣的:(Mysql,Mysql高级)