查看事务隔离级别(全局和会话):select @@global.tx_isolation,@@tx_isolation;
设置事务隔离级别:
set global transaction isolation level read committed; //全局的
set session transaction isolation level read committed; //当前会话
查看锁整体状态: show status like '%lock%';
查看引擎状态(可以查看到最后一次死锁信息):show engine innodb status\G;
查询是否锁表:show open tables where in_use>0;
查询正在使用的事务:select * from information_schema.innodb_trx\G;
查看正在锁的事务(只有单个事务,但没有竞争的时候查看不到事务):select * from information_schema.innodb_locks;
查看正在等待锁的事务:select * from information_schema.innodb_lock_waits;
一、锁详解
1、锁类型
1)按照锁机制分类
排他锁 (X锁)
-
共享锁 (S锁)
2)按照锁数据粒度分类- 行级锁
- 表级锁
- 页级锁
行级锁:开销大,加锁慢;会出现死锁;锁粒度最小,发生锁冲突的概率最低,并发度也最高。
表级锁:开销小,加锁快;不会出现死锁;锁粒度大,发生锁冲突的概率最高,并发度低。
页级锁:MySQL特有,开销和加锁时间在表锁和行锁之间;会出现死锁;锁粒度界表锁和行锁之间,并发度一般。
3)数据库事务机制
事务,保证一组操作要么全部执行,要么全部不执行,保证数据库一致性。简称ACID。
A 事务原子性(Atomicity)
事务中一组操作要么全部执行,要么全部不执行
C 事务一致性
事务的允许不改变数据的一致性
I 事务隔离性
两个事物执行时隔离的,不会交错执行
D 事务持久性
事务执行成功后,该事务对数据库所做的更改持久的保存在数据库中。
4)事务引发的并发调度问题
脏读
A事务读取B事务未提交的数据不可重复读
事务中两次读取数据不一样(包括新增、更新、删除)幻读
在同一事务里,多次查询的结果和事务开始第一次查询的状态一致。但是,如果另外一个事务同时提交了新数据,虽然本事务再次按照相同的条件查询得到相同的结果集,但是本事务指定更新时,就会发现这些新数据,这些数据就像“鬼影”一样出现。
5)事务隔离级别
-
Read uncommitted 读未提交
所有事务都可以看到其他未提交事务的执行结果。存在脏读问题。 -
Read committed 读提交
一个事务只能看见已经提交事务的内容。解决了脏读,存在不可重复读问题。
-
Repeatable read 可重复读
同一个事务多次读取时,获取到的内容一致,可重复读针对的是数据库中同一条记录而言的。解决了不可重复读问题,存在幻读。
可串行化
强制事务排序,试之不能相互冲突,解决了幻读问题
-
MySQL中四中隔离级别,分别可能产生的问题:
隔离级别 脏读 不可重复读 幻读 读未提交 √ √ √ 读已提交 × √ √ 可重复读 × × √ 可串行化 × × ×
6)聚簇索引和非聚簇索引
聚簇索引:索引的叶子节点即为行数据
1)默认主键索引就是聚簇索引
2)若没有主键索引则用一个唯一且不为空的索引列成为聚族索引
3)若唯一且不为空的索引也不存在,Innodb会隐式定义一个主键作为聚簇索引。
非聚簇索引:索引的叶子节点是主键值
其他索引如普通索引、前缀索引等为非聚族索引。
聚簇索引和非聚簇索引数据结构如下:
CREATE TABLE person
(
id
INT(11) NOT NULL,
name
VARCHAR(32) NULL DEFAULT NULL,
date
TIMESTAMP NOT NULL,
PRIMARY KEY (id
),
INDEX index_name
(name
)
);
聚族索引查找过程:直接根据聚族索引找到叶子节点上的数据
非聚族查找过程:根据非聚族索引先找到主键值(注意这里找到的是主键值,不是主键的地址),再根据主键值查找聚族索引找到数据
因为非聚族索引要扫描两次索引,所以效率会更低,这种查找也叫回表查询。
索引覆盖:只需要在一棵索引树上就能获取所有列数据,无需回表,称为索引覆盖。
索引覆盖方法:针对被查询的字段,建立索引或者联合索引。提高查询效率。
哪些场景可以利用索引覆盖来优化sql:
1)指定查询某个或者某几个列
只查询所需要的列(列上已建索引),不要使用select 来查询出所有的列数据。
2)全表count查询
使用select count(col name),不要使用select count()来统计全表。
二、索引加锁分析
1、MVCC 多版本并发控制协议(与之相对的是基于锁的并发控制Lock-Based Concurrency Control)
在MVCC并发控制中,读操作分成:快照读(Snapshot read)与当前读(Current read)。快照读,读取的是记录的可见版本(有可能是历史版本),不用加锁。当前读,读取的是记录的最新版本,并且当前读返回的记录,都会加锁,保证其他事物不会并发地修改这条记录。
InnoDb行锁是通过给索引上的索引项加锁来实现的。这也意味着只有通过索引条件检索的数据,Innodb才使用行级锁,否则,InnoDb将使用表锁。
以MySQL InnoDb为例,哪些操作是快照读?哪些是当前读?
1)快照读:简单的select操作,属于快照读,不加锁。(除一些特殊场景)
Select * from table where ?;
2)当前读:特殊的读操作,插入、更新、删除操作,属于当前读,需要加锁
Select * from table lock in share mode; (S锁)
Select * from table for udpate; (X锁)
Insert into table xxx; (X锁)
Update table set xxx where ?; (X锁)
Delete from table where ?; (X锁)
如下图,数据更新操作执行流程:
2、SQL加锁分析
SQL1:select * from t1 where id = 10;
SQL2:delete from t1 where id = 10;
上述sql加什么锁?需要根据具体场景来分析。
前提条件:
1)id列是不是主键
2)当前系统隔离级别
3)id如果不是主键,那么id是否有索引
4)id列上有二级索引,那么是唯一索引吗
5)两个SQL的执行计划是什么?索引扫描?全表扫描?
针对各种场景,具体分析:
1)在RC、RR隔离级别下,SQL1均不加锁,采用的是快照读
2)id主键 + RC
结论:id是主键时,只需要在id=10这条记录上加X锁
3)id唯一索引+RC
id是唯一索引,name是主键列。
结论:SQL需要加两个X锁,一个对应ID unique索引上的id=10的记录,另一把锁对应于聚簇索引上的name='d'的记录。
4)id为非唯一索引+RC
结论:id列上有非唯一索引,name对应的所有满足SQL查询条件的记录,都会加锁。同时这些记录的主键索引上的记录,也会被加锁。
5)id无索引+RC
结论:如果id列上没有索引,SQL会走聚簇索引的全扫描进行过滤,由于过滤是由MySQL Server层面进行的。因为每条记录,无论是否满足条件,都会被加上X锁。但是为了效率考量,MySQL做了优化,对于不满足条件的记录,会在判断后放锁,最终持有的,是满足条件的记录上的锁,但是不满足条件的记录加锁/放锁动作不会省略。同时优化也违背了2PL的约束。
6)id主键+RR
加锁与id主键+RC是一致的
7)id唯一索引+RR
加锁与id唯一索引+RC是一致的
8)id非唯一索引+RR
Repeatable Read隔离级别下,id列上有一个非唯一索引,对应SQL:delete from t1 where id = 10; 首先,通过id索引定位到第一条满足查询条件的记录,加记录上的X锁,加GAP上的GAP锁,然后加主键聚簇索引上的记录X锁,然后返回;然后读取下一条,重复进行。直至进行到第一条不满足条件的记录[11,f],此时,不需要加记录X锁,但是仍旧需要加GAP锁,最后返回结束。????
存疑
9)id为索引+RR
//www.greatytc.com/p/13f5777966dd
10)serializable
delete语句在serializable下和RR隔离级别是一致的。而select语句也会加读锁。
关于死锁:死锁的发生与否,并不在于事务中有多少条SQL语句,死锁的关键在于:两个(或以上)的Session加锁的顺序不一致。