MYSQL是在大小公司中使用率极高的开源的关系型数据库,以其良好的易用性和在分布式场景下的高性能而著称,也是所有新手在数据库入门时的产品首选。最近因为听了公司的一位师兄关于MYSQL InnoDB锁的讲座,收获很多,所以将MYSQL锁相关的必备知识在此进行梳理。这些知识不仅可以帮助面试,也可以在日常开发进行性能优化或死锁问题排查时派上用场。当然,最重要的是,在对数据进行上锁时,就能够梳理出相应的上锁流程,从而避免真正走到故障时再去排查。
本文主要包括
MYSQL主要分为客户端和服务端,其中客户端负责对服务端进行连接,服务端主要包含两个部分,其中存储引擎层(Storage Engines)决定数据在磁盘上具体的存储形式,典型的存储引擎包括InnoDb和MyISAM,而目前MYSQL甚至支持混合存储引擎,即可能一张表一半存储在InnoDb上,一半存储在MyISAM。
除此以外的其它服务端组建则不关心数据用什么形式存储,主要负责执行具体的SQL语句,
这里简单比较一下InnoDB和MyISAM这两个存储引擎。
InnoDB的特性如下:
MyISAM的特性如下:
本文主要基于InnoDB对锁的特性进行介绍。
一个查询请求在整个MYSQL服务端的链路如下:
那么SQL语句在经过解析器和优化器时是什么样的一个链路呢?
一个标准的Select SQL语句包含以下几个部分:
select t1.column1 as column1, t2,column2 as column2... from TABLE t1, TABLE t2 ... WHERE condition1 GROUP BY condition2 HAVING condition3 ORDER BY column1 LIMIT N
而这条语句的标准逻辑执行顺序如下:
这里有一点需要注意,select语句是在group by和having之后执行,因此select中as出来的列名在group by和having中是不可以引用的,但是order by中是可以引用的。
但是真正的的执行顺序和标准逻辑执行顺序并不一定相同,因为优化器会对SQL的执行顺序进行变更,从而尽可能提高SQL的执行效率。比如:
select * from table1 t1 join table2 t2 on t1.id = t2.id where t1.count > 10 and t2.count > 100
标准的执行顺序会先将表格t1和t2进行join操作,再对join后的结果针对where语句进行筛选。而优化器可能会变化一下执行顺序,先根据where t1.count > 10 and t2.count > 100
筛选出t1表和t2表中符合条件的数据,再执行join。
那么有没有办法看到SQL在真实执行的时候的执行计划呢?这就需要Explain
语法。
Explain关键字的使用方法很简单,只要将其加在具体的SELECT语句之前就可以,Explain也只能解析SELECT语句。通过Explain关键字可以观察表的索引是否合理,语句的真实执行顺序是否符合预期。Explain执行后生成的数据如下:
列名 | 含义 |
---|---|
id | SELECT语句的SQL_ID,它是指这个语句在查询中的第n条语句,如果两个id相同,则代表按照顺序执行从上到下执行,id值越大,优先级越高,越先被执行 |
select_type | SELECT语句类型, 如SIMPLE是指不使用UNION或子查询 |
table | 输出行所属的表格,derivex是指从第x步生成的衍生表 |
type | 访问类型,说明表是如何关联的 |
possible keys | 可选择的索引 |
key | 真正选择的索引。要想强制MySQL使用或忽视possible_keys列中的索引,在查询中使用FORCE INDEX、USE INDEX或者IGNORE INDEX。 |
key_len | 选中索引的长度,显示的是索引字段的最大可能长度,是根据表定义得来,而非表内检索 |
ref | 哪些列或常量被用来查找索引列上的值 |
rows | 预估需要扫描的行数 |
filtered | 预计多少比例的行数会被过滤出来 |
其中访问类型(type)按照从好到坏包括
select * from user_info where id = 2
SELECT * FROM user_info, order_info WHERE user_info.id = order_info.user_id
SELECT * FROM user_info, order_info WHERE user_info.id = order_info.user_id AND order_info.user_id = 5
value IN (SELECT primary_key FROM single_table WHERE some_expr)
在知道这些之后,使用Explain分析语句时可以按照如下思路进行分析:
这里建议看一下参考文章中的Explain实战例子文章来加深通过Explain进行优化的思路
ATOMICY原子性:事务要么全部执行,要么全部不执行
CONSISTENCY一致性:事务执行前和执行后数据状态应当一致
ISOLATION隔离性:事务之间不会相互影响
DURABILITY持久性:事务执行完成后结果不会丢失,因此需要能够对数据进行恢复
隐式事务:在autocommit为true的情况下,默认每一条语句都会开启一个事务执行,执行完毕后提交事务。因此不在事务上下文中执行select * from user where id = 1 for update
语句在语句执行完后就会释放排他锁,这在大多数情况下都是不合理的。
显式事务:每个事务以start transaction开启,以commit或rollback结束。Spring中使用@Transactional或是transactionTemplate包围的代码段
事务总共有4个隔离级别:
脏读:一个事务中未提交的语句会被另一个事务察觉
不可重复读:一个事务中提交的update语句会被另一个事务察觉
幻读:一个事务中提交的insert语句会被另一个事务察觉
锁主要分为表锁和行锁。顾名思义,表锁就是指对整张表进行上锁,而行锁则是指针对一行数据进行上锁。表锁通常在服务器层面实现,而行锁往往在存储引擎层实现。行锁并不是只对数据行上锁,还可以对索引/索引区间进行上锁,即强调的是粒度更小的锁。
锁可以分为以下四类:
意向锁和意向锁之间是兼容的,而意向锁和行锁之间也是兼容。意向锁主要是对表锁的优化。假如现在有一个事务需要对表a加排他锁,如果没有意向锁,就需要对全表进行扫描,直到找到第一个共享/排他锁。而通过判断是否有意向锁,可以极大的提高锁互斥判断的性能。加意向锁是在所有锁(行锁/表锁)之前进行判断和执行的。
行锁具体有三种实现:
只在可重复度REPEATABLE READ或以上的隔离级别下的特定操作才会取得gap lock或nextkey lock。
读已提交REPEATABLE COMMIT级别下只有record lock
MYSQL默认为RR
因此当判断语句如何加行锁时,需要根据事务隔离级别+是否使用主键/唯一键/索引进行判断。
加锁顺序本质上和索引的查询顺序是一致的
这里有一种最糟糕的情况,即如果where条件中的字段不是主键/索引/唯一索引,则会先对全部索引上排他锁,在找到符合条件的记录后,解锁不满足条件的锁。
MYSQL架构
Explain关键字
MYSQL性能优化神器Explain
Explain实战例子
详解 MySql InnoDB 中意向锁的作用
幻读