共计 47302 个字符,预计需要花费 119 分钟才能阅读完成。
本文丸趣 TV 小编为大家详细介绍“MySQL 的 SQL 优化、索引优化、锁机制、主从复制知识有哪些”,内容详细,步骤清晰,细节处理妥当,希望这篇“MySQL 的 SQL 优化、索引优化、锁机制、主从复制知识有哪些”文章能帮助大家解决疑惑,下面跟着丸趣 TV 小编的思路慢慢深入,一起来学习新知识吧。
0 存储引擎介绍
myisam 存储:如果表对事务要求不高,同时是以查询和添加为主的,我们考虑使用 myisam 存储引擎,比如 bbs 中的发帖表,回复表
需要定时进行碎片整理(因为删除的数据还是存在):optimize table table_name;
InnoDB 存储:对事务要求高,保存的数据都是重要数据,我们建议使用 INN0DB, 比如订单表,账号表.
面试问 MyISAM 和 INNODB 的区别:
1. 事务安全
2. 查询和添加速度
3. 支持全文索引
4. 锁机制
5. 外键 MyISAM 不支持外键,INNODB 支持外键.
Mermory 存储:比如我们数据变化频繁,不需要入库,同时又频繁的查询和修改,我们考虑使用 memory
查看 mysql 以提供什么存储引擎:show engines;
查看 mysql 当前默认的存储引擎:show variables like %storage_engine%
1 SQL 性能分析
SQL 性能下降原因:
1、查询语句写的烂
2、索引失效(数据变更)
3、关联查询太多 join(设计缺陷或不得已的需求)
4、服务器调优及各个参数设置(缓冲、线程数等)
通常 SQL 调优过程:
观察,至少跑 1 天,看看生产的慢 SQL 情况。
开启慢查询日志,设置阙值,比如超过 5 秒钟的就是慢 SQL,并将它抓取出来。
explain + 慢 SQL 分析。
show profile。
运维经理 or DBA,进行 SQL 数据库服务器的参数调优。
总结:
1、慢查询的开启并捕获
2、explain + 慢 SQL 分析
3、show profile 查询 SQL 在 Mysql 服务器里面的执行细节和生命周期情况
4、SQL 数据库服务器的参数调优
2 常见通用的 JOIN 查询 SQL 执行加载顺序
手写顺序:
SELECT DISTINCT
select_list
left_table join_type
JOIN right_table on join_codition //join_codition:比如员工的部门 ID 和部门表的主键 id 相同
WHERE
where_condition
GROUP BY
group_by_list
HAVING
having_condition
ORDER BY
order_by_condition
LIMIT
limit_number
MySQL 机读顺序:
1 FROM left_table
2 ON join_condition
3 join_type JOIN right_table
4 WHERE where_condition
5 GROUP BY group_by_list
6 HAVING having_condition
7 SELECT
8 DISTINCT select_list
9 ORDER BY order_by_condition
10 LIMIT limit_number
总结:
运行顺序一上一下
七种 JOIN 写法
创建表插入数据(左右主外键相连):
CREATE TABLE tbl_dept(id INT(11) NOT NULL AUTO_INCREMENT,
deptName VARCHAR(30) DEFAULT NULL,
locAdd VARCHAR(40) DEFAULT NULL,
PRIMARY KEY(id)
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
// 设置存储引擎,主键自动增长和默认文本字符集
CREATE TABLE tbl_emp (id INT(11) NOT NULL AUTO_INCREMENT,
NAME VARCHAR(20) DEFAULT NULL,
deptId INT(11) DEFAULT NULL,
PRIMARY KEY (id),
KEY fk_dept_Id (deptId)
#CONSTRAINT fk_dept_Id foreign key (deptId) references tbl_dept (Id)
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO tbl_dept(deptName,locAdd) VALUES(RD ,11);
INSERT INTO tbl_dept(deptName,locAdd) VALUES(HR ,12);
INSERT INTO tbl_dept(deptName,locAdd) VALUES(MK ,13);
INSERT INTO tbl_dept(deptName,locAdd) VALUES(MIS ,14);
INSERT INTO tbl_dept(deptName,locAdd) VALUES(FD ,15);
INSERT INTO tbl_emp(NAME,deptId) VALUES(z3 ,1);
INSERT INTO tbl_emp(NAME,deptId) VALUES(z4 ,1);
INSERT INTO tbl_emp(NAME,deptId) VALUES(z5 ,1);
INSERT INTO tbl_emp(NAME,deptId) VALUES(w5 ,2);
INSERT INTO tbl_emp(NAME,deptId) VALUES(w6 ,2);
INSERT INTO tbl_emp(NAME,deptId) VALUES(s7 ,3);
INSERT INTO tbl_emp(NAME,deptId) VALUES(s8 ,4);
INSERT INTO tbl_emp(NAME,deptId) VALUES(s9 ,51);
#查询执行后结果
mysql select * from tbl_dept;
+----+----------+--------+
| id | deptName | locAdd |
+----+----------+--------+
| 1 | RD | 11 |
| 2 | HR | 12 |
| 3 | MK | 13 |
| 4 | MIS | 14 |
| 5 | FD | 15 |
+----+----------+--------+
5 rows in set (0.00 sec)
mysql select * from tbl_emp;
+----+------+--------+
| id | NAME | deptId |
+----+------+--------+
| 1 | z3 | 1 |
| 2 | z4 | 1 |
| 3 | z5 | 1 |
| 4 | w5 | 2 |
| 5 | w6 | 2 |
| 6 | s7 | 3 |
| 7 | s8 | 4 |
| 8 | s9 | 51 |
+----+------+--------+
8 rows in set (0.00 sec)
1、inner join:只有 deptId 和 id 的共有部分
2、left join(全 A):前七条共有数据;第八条 a 表独有数据,b 表补 null
3、right join(全 B):前七条共有数据;第八条 b 表独有数据,a 表补 null
4、左 join 独 A:表 A 独有部分
5、右 join 独 B:表 B 独有部分
6、full join:MySQL 不支持 full join,用全 a + 全 b,union 去重中间部分
union 关键字可以合并去重
7、A、B 各自独有集合
3 索引介绍 3.1 索引是什么
MySQL 官方对索引的定义为:索引(Index)是帮助 MySQL 高效获取数据的数据结构(索引的本质是数据结构,排序 + 查询两种功能)。
索引的目的在于提高查询效率,可以类比字典。
如果要查“mysql”这个单词,我们肯定需要定位到 m 字母,然后从下往下找到 y 字母,再找到剩下的 sql。
如果没有索引,那么你可能需要逐个逐个寻找,如果我想找到 Java 开头的单词呢?或者 Oracle 开头的单词呢?
是不是觉得如果没有索引,这个事情根本无法完成?
索引可以理解为:排好序的快速查找数据结构
下图就是一种可能的索引方式示例:
假如:找 4 号这本书,扫码得到对应的编号为 91,91 比 34 大往右边找,91 比 89 大往右边找,然后找到(比较三次后就可以找到,然后检索出对应的物理地址)
为了加快 Col2 的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到相应数据,从而快速的检索出符合条件的记录
结论:在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。
我们平常所说的索引,如果没有特别指明,都是指 B 树(多路搜索树,并不一定是二叉的)结构组织的索引。其中聚集索引,次要索引,覆盖索引,复合索引,前缀索引,唯一索引默认都是使用 B + 树索引,统称索引。当然,除了 B + 树这种类型的索引之外,还有哈稀索引 (hash index) 等
3.2 索引优劣势
优势:
类似大学图书馆建书目索引,提高数据检索的效率,降低数据库的 IO 成本。
通过索引列对数据进行排序,降低数据排序的成本,降低了 CPU 的消耗。
劣势:
实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的(占空间)
虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行 INSERT、UPDATE 和 DELETE。因为更新表时,MySQL 不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息。
索引只是提高效率的一个因素,如果你的 MysQL 有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询
3.3 索引分类和建索引命令语句
主键索引:索引值必须是唯一的,且不能为 NULL
第一种:CREATE TABLE table_name(id int PRIMARY KEY aoto_increment,name varchar(10));
第二种:ALTER TABLE table_name ADD PRIMARY KEY (columnName);
普通索引:索引值可出现多次
第一种:CREATE INDEX index_name on table_name(columnName);
第二种:ALTER TABLE table_name ADD INDEX index_name (columnName);
全文索引:主要是针对文本的检索,如:文章,全文索引只针对 MyISAM 引擎有效,并且只针对英文内容生效
建表时创建
# 建表
CREATE TABLE articles(
id INT UNSIGNED ATUO_INCREMENT NOT NULL PRIMARY KEY,
title VARCHAR(200),
body TEXT,
FULLTEXT(title,body)
)engine=myisam charset utf8; #指定引擎
select * from articles where match(title,body) against( 英文内容 # 只针对英语内容生效
#1、在 mysql 中 fultext 索引只针对 myisam 生效
#2、mysq1 自己提供的 flltext 只针对英文生效 - sphinx (coreseek)技术处理中文工
#3、使用方法是 match(字段名...) against(‘关键字)
#4、全文索引一个叫停止词,因为在一个文本中创建索引是一个无穷大的数,因此对一些常用词和字符就不会创建,这些词称为停止词
ALTER TABLE table_name ADD FULLTEXT index_name (columnName);
唯一索引:索引列的值必须唯一,但允许有空值 NULL,并可以有多个。
第一种:CREATE UNIQUE INDEX index_name ON table_name(columnName);
第二种:ALTER TABLE table_name ADD UNIQUE INDEX index_name ON (columnName);
单值索引:即一个索引只包含单个列,一个表可以有多个单列索引。
第一种:CREATE INDEX index_name ON table_name(columnName);
第二种:ALTER TABLE table_name ADD INDEX index_name ON (columnName);
select * from user where name=
// 经常查 name 字段,为其建索引
create index idx_user_name on user(name);
复合索引:即一个索引包含多个列
第一种:CREATE INDEX index_name ON table_name(columnName1,columnName2…);
第二种:ALTER TABLE table_name ADD INDEX index_name ON (columnName1,columnName2…);
select * from user where name= and email=
// 经常查 name 和 email 字段,为其建索引
create index idx_user_name on user(name, email);
查询索引
第一种:SHOW INDEX FROM table_name;
第二种:SHOW KEYS FROM table_name;
删除索引
第一种:DROP INDEX index_name ON table_name;
第二种:ALTER TABLE table_name DROP INDEX index_name;
删除主键索引:ALTER TBALE table_name DROP PRIMARY KEY;
3.4 索引结构与检索原理
MySQL 索引结构:
BTree 索引
Hash 索引
full-text 全文索引
R-Tree 索引
初始化介绍
一颗 b + 树,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示), 如磁盘块 1 包含数据项 17 和 35,包含指针 P1、P2、P3,
P1 表示小于 17 的磁盘块,P2 表示在 17 和 35 之间的磁盘块,P3 表示大于 35 的磁盘块。
真实的数据存在于叶子节点:3、5、9、10、13、15、28、29、36、60、75、79、90、99。
非叶子节点只不存储真实的数据,只存储指引搜索方向的数据项,如 17、35 并不真实存在于数据表中。
查找过程
如果要查找数据项 29,那么首先会把磁盘块 1 由磁盘加载到内存,此时发生一次 IO。在内存中用二分查找确定 29 在 17 和 35 之间,锁定磁盘块 1 的 P2 指针,内存时间因为非常短(相比磁盘的 IO)可以忽略不计,通过磁盘块 1 的 P2 指针的磁盘地址把磁盘块 3 由磁盘加载到内存,发生第二次 IO,29 在 26 和 30 之间,锁定磁盘块 3 的 P2 指针,通过指针加载磁盘块 8 到内存,发生第三次 IO,同时内存中做二分查找找到 29,结束查询,总计三次 IO
真实的情况是,3 层的 b + 树可以表示上百万的数据,如果上百万的数据查找只需要三次 IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次 IO,那么总共需要百万次的 IO,显然成本非常非常高
3.5 哪些情况适合建索引
主键自动建立唯一索引
频繁作为查询条件的字段应该创建索引
查询中与其它表关联的字段,外键关系建立索引
单键 / 组合索引的选择问题,who?(在高并发下倾向创建组合索引)
查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
查询中统计或者分组字段
3.6 哪些情况不适合建索引
Where 条件里用不到的字段不创建索引
表记录太少(300w 以上建)
经常增删改的表(提高了查询速度,同时却会降低更新表的速度,如对表进行 INSERT、UPDATE 和 DELETE。因为更新表时,MySQL 不仅要保存数据,还要保存一下索引文件)
数据重复且分布平均的表字段,因此应该只为最经常查询和最经常排序的数据列建立索引。注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。(比如:国籍、性别)
假如一个表有 10 万行记录,有一个字段 A 只有 T 和 F 两种值,且每个值的分布概率天约为 50%,那么对这种表 A 字段建索引一般不会提高数据库的查询速度。
索引的选择性是指索引列中不同值的数目与表中记录数的比。如果一个表中有 2000 条记录,表索引列有 1980 个不同的值,那么这个索引的选择性就是 1980/2000=0.99。一个索引的选择性越接近于 1,这个索引的效率就越高
4 性能分析 4.1 性能分析前提知识
MySQL Query Optimizer(查询优化器)[ˈkwɪəri] [ˈɒptɪmaɪzə]
Mysql 中专门负责优化 SELECT 语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的 Query 提供他认为最优的执行计划(他认为最优的数据检索方式,但不见得是 DBA 认为是最优的, 这部分最耗费时间)
当客户端向 MySQL 请求一条 Query,命令解析器模块完成请求分类,区别出是 SELECT 并转发给 MySQL Query Optimizer 时,MySQL Query Optimizer 首先会对整条 Query 进行优化,处理掉一些常量表达式的预算直接换算成常量值。并对 Query 中的查询条件进行简化和转换,如去掉一些无用或显而易见的条件、结构调整等。然后分析 Query 中的 Hint 信息(如果有),看显示 Hint 信息是否可以完全确定该 Query 的执行计划。如果没有 Hint 或 Hint 信息还不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据 Query 进行写相应的计算分析,然后再得出最后的执行计划
MySQL 常见瓶颈:
CPU:CPU 在饱和的时候一般发生在数据装入内存或从磁盘上读取数据时候
IO:磁盘 I / O 瓶颈发生在装入数据远大于内存容量的时候
服务器硬件的性能瓶颈:top,free,iostat 和 vmstat 来查看系统的性能状态
4.2 Explain 使用简介
使用 EXPLAIN 关键字可以模拟优化器执行 SQL 查询语句,从而知道 MySQL 是如何处理你的 SQL 语句的。分析你的查询语句或是表结构的性能瓶颈
官网地址
Explain 的作用:
表的读取顺序
数据读取操作的操作类型
哪些索引可以使用
哪些索引被实际使用
表之间的引用
每张表有多少行被优化器查询
使用 Explain:
explain + sql 语句
执行计划包含的信息(重点):| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
mysql select * from tbl_emp;
+----+------+--------+
| id | NAME | deptId |
+----+------+--------+
| 1 | z3 | 1 |
| 2 | z4 | 1 |
| 3 | z5 | 1 |
| 4 | w5 | 2 |
| 5 | w6 | 2 |
| 6 | s7 | 3 |
| 7 | s8 | 4 |
| 8 | s9 | 51 |
+----+------+--------+
8 rows in set (0.00 sec)
mysql explain select * from tbl_emp;
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | tbl_emp | NULL | ALL | NULL | NULL | NULL | NULL | 8 | 100.00 | NULL |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
4.3 执行计划包含的信息字段解释(重中之重)
执行计划包含的信息(重点):| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
面试重点:id、type、key、rows、Extra
id(表的读取顺序)
select 查询的序列号,包含一组数字,表示查询中执行 select 子句或操作表的顺序
三种情况:
1、id 相同,执行顺序由上至下(t1、t3、t2)
2、id 不同,如果是子查询,id 的序号会递增,id 值越大优先级越高,越先被执行(t3、t1、t2)
3、id 相同不同,同时存在。先走数字大的,数字相同的由上至下(t3、s1、t2)
select_type(数据读取操作的操作类型)
查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询。
SIMPLE [ˈsɪnpl]:简单的 select 查询, 查询中不包含子查询或者 UNION
PRIMARY:查询中若包含任何复杂的子部分,最外层查询则被标记为(最后加载的那个)
SUBQUERY [ˈkwɪəri]:在 SELECT 或 WHERE 列表中包含了子查询
DERIVED [dɪˈraɪvd]:在 FROM 列表中包含的子查询被标记为 DERIVED(衍生)MySQL 会递归执行这些子查询,把结果放在临时表里
UNION [ˈjuːniən]:若第二个 SELECT 出现在 UNION 之后,则被标记为 UNION;若 UNION 包含在 FROM 子句的子查询中外层 SELECT 将被标记为:DERIVED
UNION RESULT [rɪˈzʌlt]:从 UNION 表获取结果的 SELECT(两个 select 语句用 UNION 合并)
table(显示执行的表名)
显示这一行的数据是关于哪张表的
type(访问类型排列)
显示查询使用了何种类型
访问类型排列:system const eq_ref ref fulltext ref_or_null index_merge unique_subquery index_subquery range index ALL
type 常用八种类型:
结果值从最好到最坏依次是(重点)::system const eq_ref ref range index ALL
一般来说,得保证查询至少达到 range 级别,最好能达到 ref
详细说明
system:表只有一行记录(等于系统表),这是 const 类型的特列,平时不会出现,这个也可以忽略不计。
const:表示通过索引一次就找到了,const 用于比较 primary key 或者 unique 索引。因为只匹配一行数据,所以很快如将主键置于 where 列表中,MySQL 就能将该查询转换为一个常量。
eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描。
ref:非唯一性索引扫描,返回匹配某个单独值的所有行,本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体
range:只检索给定范围的行, 使用一个索引来选择行。key 列显示使用了哪个索引一般就是在你的 where 语句中出现了 between、、、in 等的查询。这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束语另一点,不用扫描全部索引
index:Full Index Scan,index 与 ALL 区别为 index 类型只遍历索引列。这通常比 ALL 快,因为索引文件通常比数据文件小(也就是说虽然 all 和 Index 都是读全表,但 index 是从索引中读取的,而 all 是从硬盘中读的)
all:Full Table Scan,将遍历全表以找到匹配的行
工作案例:经理这条 SQL 我跑了一下 Explain 分析,在系统上可能会有 ALL 全表扫描的情况,建议尝试一下优化。我把这条 SQL 改了改,我优化后是这么写,这个效果已经从 ALL 变成了…
possible_keys(哪些索引可以使用)
显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段火若存在索引,则该索引将被列出,但不一定被查询实际使用(系统认为理论上会使用某些索引)
key(哪些索引被实际使用)
实际使用的索引。如果为 NULL,则没有使用索引(要么没建,要么建了失效)
查询中若使用了覆盖索引,则该索引仅出现在 key 列表中
覆盖索引:建的索引字段和查询的字段一致,如下图
key_len(消耗的字节数)
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好
key_len 显示的值为索引字段的最大可能长度,并非实际使用长度,即 key_len 是根据表定义计算而得,不是通过表内检索出的
ref(表之间的引用)
显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。
rows(每张表有多少行被优化器查询)
根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数(越小越好)
未建索引时:
建索引后:扫描行数减少
Extra [ˈekstrə]
包含不适合在其他列中显示但十分重要的额外信息
信息种类:Using filesort、Using temporary、Using index、Using where、Using join buffer、impossible where、select tables optimized away、distinct
Using filesort(需要优化)
说明 mysql 会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL 中无法利用索引完成的排序操作称为 文件排序
Using temporary(需要优化)
使了用临时表保存中间结果,MysQL 在对查询结果排序时使用临时表。常见于排序 order by 和分组查询 group by
Using index(good)
表示相应的 select 操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错!
情况一:
情况二:
覆盖索引 / 索引覆盖(Covering Index)。
理解方式一:就是 select 的数据列只用从索引中就能够取得,不必读取数据行,MySQL 可以利用索引返回 select 列表中的字段,而不必根据索引再次读取数据文件, 换句话说查询列要被所建的索引覆盖。
理解方式二:索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据;当能通过读取索引就可以得到想要的数据,那就不需要读取行了。一个索引包含了(或覆盖了)满足查询结果的数据就叫做覆盖索引。
注意:
如果要使用覆盖索引,一定要注意 select 列表中只取出需要的列,不可 select*
因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降
Using where:表明使用了 where 过滤。
Using join buffer:使用了连接缓存
impossible where:where 子句的值总是 false,不能用来获取任何元组
select tables optimized away
在没有 GROUPBY 子句的情况下,基于索引优化 MIN/MAX 操作,或者对于 MyISAM 存储引擎优化 COUNT(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
distinct
优化 distinct 操作,在找到第一匹配的元组后即停止找同样值的动作。
练习
写出下图的表的执行顺序
第一行(执行顺序 4):id 列为 1,表示是 union 里的第一个 select,select_type 列的 primary 表示该查询为外层查询,table 列被标记为,表示查询结果来自一个衍生表,其中 derived3 中 3 代表该查询衍生自第三个 select 查询,即 id 为 3 的 select。【select d1.name…】
第二行(执行顺序 2):id 为 3,是整个查询中第三个 select 的一部分。因查询包含在 from 中,所以为 derived。【select id,namefrom t1 where other_column=’’】
第三行(执行顺序 3):select 列表中的子查询 select_type 为 subquery,为整个查询中的第二个 select。【select id from t3】
第四行(执行顺序 1):select_type 为 union,说明第四个 select 是 union 里的第二个 select,最先执行【select name,id from t2】
第五行(执行顺序 5):代表从 union 的临时表中读取行的阶段,table 列的 union1,4 表示用第一个和第四个 select 的结果进行 union 操作。【两个结果 union 操作】
5 索引优化 5.1 索引单表优化案例
建表:
CREATE TABLE IF NOT EXISTS article(id INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
author_id INT(10) UNSIGNED NOT NULL,
category_id INT(10) UNSIGNED NOT NULL,
views INT(10) UNSIGNED NOT NULL,
comments INT(10) UNSIGNED NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL
INSERT INTO article(author_id,category_id,views,comments,title,content)
VALUES
(1,1,1,1, 1 , 1),
(2,2,2,2, 2 , 2),
(1,1,3,3, 3 , 3
mysql select * from article;
+----+-----------+-------------+-------+----------+-------+---------+
| id | author_id | category_id | views | comments | title | content |
+----+-----------+-------------+-------+----------+-------+---------+
| 1 | 1 | 1 | 1 | 1 | 1 | 1 |
| 2 | 2 | 2 | 2 | 2 | 2 | 2 |
| 3 | 1 | 1 | 3 | 3 | 3 | 3 |
+----+-----------+-------------+-------+----------+-------+---------+
3 rows in set (0.00 sec)
案例
要求:查询 category_id 为 1 且 comments 大于 1 的情况下,views 最多的 article_id
// 功能实现
mysql SELECT id, author_id FROM article WHERE category_id = 1 AND comments 1 ORDER BY views DESC LIMIT 1;
+----+-----------+
| id | author_id |
+----+-----------+
| 3 | 1 |
+----+-----------+
1 row in set (0.00 sec)
//explain 分析
mysql explain SELECT id, author_id FROM article WHERE category_id = 1 AND comments 1 ORDER BY views DESC LIMIT 1;
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
| 1 | SIMPLE | article | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 33.33 | Using where; Using filesort |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
1 row in set, 1 warning (0.00 sec)
结论:很显然,type 是 ALL,即最坏的情况。Extra 里还出现了 Using filesort,也是最坏的情况。优化是必须的
开始优化
新建索引(给 WHERE 语句后使用的字段添加索引)
创建方式:
create index idx_article_ccv on article(category_id,comments,views);
ALTER TABLE article ADD INDEX idx_article_ccv (category_id , comments , views
索引用处不大,删除:DROP INDEX idx_article_ccv ON article;
结论:
type 变成了 range,这是可以忍受的。但是 extra 里使用 Using filesort 仍是无法接受的。
但是我们已经建立了索引,为啥没用呢?
这是因为按照 BTree 索引的工作原理,先排序 category_id,如果遇到相同的 category_id 则再排序 comments, 如果遇到相同的 comments 则再排序 views。
当 comments 字段在联合索引里处于中间位置时,因 comments 1 条件是一个范围值(所谓 range),MySQL 无法利用索引再对后面的 views 部分进行检索,即 range 类型查询字段后面的索引无效。
改进
上次创建索引相比,这次不为 comments 字段创建索引
结论:type 变为了 ref,ref 中是 const,Extra 中的 Using filesort 也消失了,结果非常理想
5.2 索引两表优化案例
建表:
CREATE TABLE IF NOT EXISTS class(id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
card INT(10) UNSIGNED NOT NULL,
PRIMARY KEY(id)
CREATE TABLE IF NOT EXISTS book(bookid INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
card INT(10) UNSIGNED NOT NULL,
PRIMARY KEY(bookid)
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
mysql select * from class;
+----+------+
| id | card |
+----+------+
| 1 | 17 |
| 2 | 2 |
| 3 | 18 |
| 4 | 4 |
| 5 | 4 |
| 6 | 8 |
| 7 | 9 |
| 8 | 1 |
| 9 | 18 |
| 10 | 6 |
| 11 | 15 |
| 12 | 15 |
| 13 | 12 |
| 14 | 15 |
| 15 | 18 |
| 16 | 2 |
| 17 | 18 |
| 18 | 5 |
| 19 | 7 |
| 20 | 1 |
| 21 | 2 |
+----+------+
21 rows in set (0.00 sec)
mysql select * from book;
+--------+------+
| bookid | card |
+--------+------+
| 1 | 8 |
| 2 | 14 |
| 3 | 3 |
| 4 | 16 |
| 5 | 8 |
| 6 | 12 |
| 7 | 17 |
| 8 | 8 |
| 9 | 10 |
| 10 | 3 |
| 11 | 4 |
| 12 | 12 |
| 13 | 9 |
| 14 | 7 |
| 15 | 6 |
| 16 | 8 |
| 17 | 3 |
| 18 | 11 |
| 19 | 5 |
| 20 | 11 |
+--------+------+
20 rows in set (0.00 sec)
开始 Explain 分析:type 都是 all,需要优化(总有一个表来添加索引驱动)
左连接为左表加索引
删除索引:drop index y on class;
左连接为右表添加索引
删除索引:drop index Y on book;
案例:如果别人建的索引位置不对,只需要自己查询时调整左右表的顺序即可
结论:
第二行的 type 变为了 ref,rows 也变少了,优化比较明显。这是由左连接特性决定的。LEFT JOIN 条件用于确定如何从右表搜索行,左边一定都有,所以右边是我们的关键点,一定需要在右表建立索引(小表驱动大表)。
左连接,右表加索引
同理:右连接,左表加索引
5.3 索引三表优化案例
建表:
CREATE TABLE IF NOT EXISTS phone(phoneid INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
card INT(10) UNSIGNED NOT NULL,
PRIMARY KEY(phoneid)
)ENGINE=INNODB;
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
mysql select * from phone;
+---------+------+
| phoneid | card |
+---------+------+
| 1 | 10 |
| 2 | 13 |
| 3 | 17 |
| 4 | 5 |
| 5 | 12 |
| 6 | 7 |
| 7 | 15 |
| 8 | 17 |
| 9 | 17 |
| 10 | 14 |
| 11 | 19 |
| 12 | 13 |
| 13 | 5 |
| 14 | 8 |
| 15 | 2 |
| 16 | 8 |
| 17 | 11 |
| 18 | 14 |
| 19 | 13 |
| 20 | 5 |
+---------+------+
20 rows in set (0.00 sec)
用上一节两个表,删除他们的索引:
三表查询语句应为:SELECT * FROM class LEFT JOIN book ON class.card = book.card LEFT JOIN phone ON book.card = phone.card;
创建索引:
应该为第一个 LFET JOIN 的右表 book 建索引
alter table `book` add index Y(`card`);
应该为第二个 LFET JOIN 的右表 phone 建索引
alter table `phone` add index z(`card`);
Explain 分析:
后 2 行的 type 都是 ref 且总 rows 优化很好,效果不错。因此索引最好设置在需要经常查询的字段中
结论:
Join 语句的优化
尽可能减少 Join 语句中的 NestedLoop 的循环总次数:“永远用小结果集驱动大的结果集(比如:书的类型表驱动书的名称表)”。
优先优化 NestedLoop 的内层循环,保证 Join 语句中被驱动表上 Join 条件字段已经被索引。
当无法保证被驱动表的 Join 条件字段被索引且内存资源充足的前提下,不要太吝惜 JoinBuffer 的设置
5.4 索引失效
建表:
CREATE TABLE staffs(
id INT PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(24) NOT NULL DEFAULT COMMENT 姓名 ,
`age` INT NOT NULL DEFAULT 0 COMMENT 年龄 ,
`pos` VARCHAR(20) NOT NULL DEFAULT COMMENT 职位 ,
`add_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 入职时间
)CHARSET utf8 COMMENT 员工记录表
INSERT INTO staffs(`name`,`age`,`pos`,`add_time`) VALUES(z3 ,22, manager ,NOW());
INSERT INTO staffs(`name`,`age`,`pos`,`add_time`) VALUES(July ,23, dev ,NOW());
INSERT INTO staffs(`name`,`age`,`pos`,`add_time`) VALUES(2000 ,23, dev ,NOW());
ALTER TABLE staffs ADD INDEX index_staffs_nameAgePos(`name`,`age`,`pos`);
索引失效案例:
1、全值匹配我最爱
2、最佳左前缀法则(重要!):如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过复合索引中间列。
中间列不能断:
3、不在索引列上做任何操作(计算、函数、(自动 or 手动)类型转换),会导致索引失效而转向全表扫描。
4、存储引擎不能使用索引中范围条件右边的列(范围之后全失效,范围列并不是做的查询而是排序)。
5、尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少 select *。
6、mysql 在使用不等于(!= 或者)的时候无法使用索引会导致全表扫描。
7、is null, is not null 也无法使用索引。
8、like 以通配符开头(’%abc…’),mysql 索引失效会变成全表扫描的操作(% 写在最右边索引不会失效,或覆盖索引)。
问题:解决 like % 字符串 % 时索引不被使用的方法?采用覆盖索引的方法!
建表:
CREATE TABLE `tbl_user`(`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(20) DEFAULT NULL,
`age`INT(11) DEFAULT NULL,
`email` VARCHAR(20) DEFAULT NULL,
PRIMARY KEY(`id`)
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO tbl_user(`name`,`age`,`email`)VALUES( 1aa1 ,21, a@163.com
INSERT INTO tbl_user(`name`,`age`,`email`)VALUES( 2bb2 ,23, b@163.com
INSERT INTO tbl_user(`name`,`age`,`email`)VALUES( 3cc3 ,24, c@163.com
INSERT INTO tbl_user(`name`,`age`,`email`)VALUES( 4dd4 ,26, d@163.com
mysql select * from tbl_user;
+----+------+------+-----------+
| id | name | age | email |
+----+------+------+-----------+
| 1 | 1aa1 | 21 | a@163.com |
| 2 | 2bb2 | 23 | b@163.com |
| 3 | 3cc3 | 24 | c@163.com |
| 4 | 4dd4 | 26 | d@163.com |
+----+------+------+-----------+
4 rows in set (0.00 sec)
创建索引:
CREATE INDEX idx_user_nameAge ON tbl_user(NAME,age);
索引成功使用:
索引失效:
总结:% 写在最右边,如果非要写在最左边,就使用覆盖索引
9、字符串不加单引号索引失效。
Explain 分析:
10、少用 or,用它来连接时会索引失效
5.5 索引面试题分析
建表:
create table test03(
id int primary key not null auto_increment,
c1 char(10),
c2 char(10),
c3 char(10),
c4 char(10),
c5 char(10)
insert into test03(c1,c2,c3,c4,c5) values ( a1 , a2 , a3 , a4 , a5
insert into test03(c1,c2,c3,c4,c5) values ( b1 , b2 , b3 , b4 , b5
insert into test03(c1,c2,c3,c4,c5) values ( c1 , c2 , c3 , c4 , c5
insert into test03(c1,c2,c3,c4,c5) values ( d1 , d2 , d3 , d4 , d5
insert into test03(c1,c2,c3,c4,c5) values ( e1 , e2 , e3 , e4 , e5
// 查看表结构
mysql select * from test03;
+----+------+------+------+------+------+
| id | c1 | c2 | c3 | c4 | c5 |
+----+------+------+------+------+------+
| 1 | a1 | a2 | a3 | a4 | a5 |
| 2 | b1 | b2 | b3 | b4 | b5 |
| 3 | c1 | c2 | c3 | c4 | c5 |
| 4 | d1 | d2 | d3 | d4 | d5 |
| 5 | e1 | e2 | e3 | e4 | e5 |
+----+------+------+------+------+------+
5 rows in set (0.00 sec)
建索引:
create index idx_test03_c1234 on test03(c1,c2,c3,c4);
// 查看索引
mysql show index from test03;
+--------+------------+------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+--------+------------+------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| test03 | 0 | PRIMARY | 1 | id | A | 2 | NULL | NULL | | BTREE | | |
| test03 | 1 | idx_test03_c1234 | 1 | c1 | A | 5 | NULL | NULL | YES | BTREE | | |
| test03 | 1 | idx_test03_c1234 | 2 | c2 | A | 5 | NULL | NULL | YES | BTREE | | |
| test03 | 1 | idx_test03_c1234 | 3 | c3 | A | 5 | NULL | NULL | YES | BTREE | | |
| test03 | 1 | idx_test03_c1234 | 4 | c4 | A | 5 | NULL | NULL | YES | BTREE | | |
+--------+------------+------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
5 rows in set (0.00 sec)
1)逐一增加列
2)交换条件顺序不影响索引,但最好按照建索引顺序来写 SQL
3) 限定范围
4)order by
5)group by
定值、范围还是排序,一般 order by 是给个范围
group by 基本上都需要进行排序,会有临时表产生
建议:
对于单值索引,尽量选择针对当前 query 过滤性更好的索引。
在选择组合索引的时候,当前 Query 中过滤性最好的字段在索引字段顺序中,位置越靠左越好。
在选择组合索引的时候,尽量选择可以能够包含当前 query 中的 where 字句中更多字段的索引。
尽可能通过分析统计信息和调整 query 的写法来达到选择合适索引的目的。
5.6 总结
优化总结口诀
全值匹配我最爱,最左前缀要遵守;
带头大哥不能死,中间兄弟不能断;
索引列上少计算,范围之后全失效;
LIKE 百分写最右,覆盖索引不写 *;
不等空值还有 OR,索引影响要注意;
VAR 引号不可丢,SQL 优化有诀窍。
6 查询截取分析 6.1 小表驱动大表
EXISTS [ɪɡˈzɪsts]语法:SELECT …FROM table WHERE EXISTS (subquery)
该语法可以理解为:将主查询的数据,放到子查询中做条件验证,根据验证结果(TRUE 或 FALSE)来决定主查询的数据结果是否得以保留
提示:
EXSTS(subquey) 只返回 TRUE 或 FALSE,因此子查询中的 SELECT * 也可以是 SELECT 1 或 select‘X’,官方说法是实际执行时会忽略 SELECT 清单,因此没有区别。
EXISTS 子查询的实际执行过程可能经过了优化而不是我们理解上的逐条对比,如果担忧效率问题,可进行实际检验以确定是否有效率问题。
EXISTS 子查询往往也可以用条件表达式,其他子查询或者 JOIN 来替代,何种最优需要具体问题具体分析
in 和 exists 用法:
6.2 Order by 关键字排序优化
1、ORDER BY 之后子句,尽量使用 Index 方式排序,避免使用 FileSort 方式排序
建表:
create table tblA(
#id int primary key not null auto_increment,
age int,
birth timestamp not null
insert into tblA(age, birth) values(22, now());
insert into tblA(age, birth) values(23, now());
insert into tblA(age, birth) values(24, now());
create index idx_A_ageBirth on tblA(age, birth);
mysql select * from tblA;
+------+---------------------+
| age | birth |
+------+---------------------+
| 22 | 2021-04-04 19:31:45 |
| 23 | 2021-04-04 19:31:45 |
| 24 | 2021-04-04 19:31:45 |
+------+---------------------+
3 rows in set (0.00 sec)
mysql show index from tblA;
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| tbla | 1 | idx_A_ageBirth | 1 | age | A | 3 | NULL | NULL | YES | BTREE | | |
| tbla | 1 | idx_A_ageBirth | 2 | birth | A | 3 | NULL | NULL | | BTREE | | |
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
2 rows in set (0.00 sec)
关注点:是 order by 之后会不会产生 Using filesort
MySQL 支持二种方式的排序,FileSort 和 lIndex,Index 效率高,它指 MySQL 扫描索引本身完成排序。FileSort 方式效率较低。
ORDER BY 满足两情况,会使用 Index 方式排序:
ORDER BY 语句使用索引最左前列。
使用 where 子句与 Order BY 子句条件列组合满足索引最左前列。
2、尽可能在索引上完成排序操作,遵照建索引的最佳左前缀
3、如果不在索引列上,mysql 的 filesort 有两种算法(自动启动)
双路排序
MySQL4.1 之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据,读取行指针和 OrderBy 列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读对应的数据输出。
从磁盘取排序字段,在 buffer 进行排序,再从磁盘取其他字段。
取一批数据,要对磁盘进行了两次扫描,众所周知,I\O 是很耗时的,所以在 mysql4.1 之后,出现了第二种改进的算法,就是单路排序
单路排序
从磁盘读取查询需要的所有列,按照 order by 列在 buffer 对它们进行排序,然后扫描排序压的列表进行输出,它的效率更快一些,避免了第二次读取数据。并且把随机 IO 变成了顺序 IO, 但是它会使用更多的空间,因为它把每一行都保存在内存中了
结论及引申出的问题
由于单路是后出的,总体而言好过双路
但是用单路有问题,在 sort_buffer 中,方法 B 比方法 A 要多占用很多空间,因为方法 B 是把所有字段都取出, 所以有可能取出的数据的总大小超出了 sort_buffer 的容量,导致每次只能取 sort_buffer 容量大小的数据,进行排序(创建 tmp 文件,多路合并),排完再取取
sort_buffer 容量大小,再排……从而多次 I /O。
本来想省一次 I / O 操作,反而导致了大量的 I / O 操作,反而得不偿失
4、优化策略
增大 sort_buffer_size 参数的设置
增大 max_length_for_sort_data 参数的设置
Why?
5、小总结:
6.3 Group by 优化
group by 实质是先排序后进行分组,遵照索引建的最佳左前缀。
当无法使用索引列,增大 max_length_for_sort_data 参数的设置 + 增大 sort_buffer_size 参数的设置。
where 高于 having,能写在 where 限定的条件就不要去 having 限定了
6.4 慢查询日志(重点)
介绍:
MySQL 的慢查询日志是 MySQL 提供的一种日志记录,它用来记录在 MySQL 中响应时间超过阀值的语句,具体指运行时间超过 long_query_time 值的 SQL,则会被记录到慢查询日志中。
具体指运行时间超过 long_query_time 值的 SQL,则会被记录到慢查询日志中。long_query_time 的默认值为 10,意思是运行 10 秒以上的语句。
由他来查看哪些 SQL 超出了我们的最大忍耐时间值,比如一条 sql 执行超过 5 秒钟,我们就算慢 SQL,希望能收集超过 5 秒的 sql,结合之前 explain 进行全面分析
操作说明:
默认情况下,MySQL 数据库没有开启慢查询日速,需要我们手动来设置这个参数。
当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件。
查看是否开启及如何开启:
默认:SHOW VARIABLES LIKE %slow_query_log% [ˈveəriəbls]
开启:set global slow_query_log=1;,只对当前数据库生效,如果 MySQL 重启后则会失效
如果要永久生效,就必须修改配置文件 my.cnf(其它系统变量也是如此)
修改 my.cnf 文件,[mysqld] 下增加或修改参数 slow_query_log 和 slow_query_log_file 后,然后重启 MySQL 服务器。也即将如下两行配置进 my.cnf 文件
slow_query_log =1slow_query_log_file=/var/lib/mysqatguigu-slow.log
关于慢查询的参数 slow_query_log_file,它指定慢查询日志文件的存放路径,系统默认会给一个缺省的文件 host_name-slow.log(如果没有指定参数 slow_query_log_file 的话)
开启了慢查询日志后,什么样的 SQL 才会记录到慢查询日志里面呢?
这个是由参数 long_query_time 控制,默认情况下 long_query_time 的值为 10 秒,命令:SHOW VARIABLES LIKE long_query_time%
可以使用命令修改,也可以在 my.cnf 参数里面修改。
假如运行时间正好等于 long_query_time 的情况,并不会被记录下来。也就是说,在 mysql 源码里是判断大于 long_query_time,而非大于等于。
命名修改慢 SQL 阈值时间:set global long_query_time=3; [ˈɡləʊbl]
看不到修改情况的话,重开连接,或者换一个语句:show global variables like long_query_time
记录慢 SQL 并后续分析:
假设我们成功设置慢 SQL 阈值时间为 3 秒(set global long_query_time=3;)。
模拟超时 SQL:select sleep(4);
查询当前系统中有多少条慢查询记录:show global status like %Slow_queries% [ˈsteɪtəs]
在配置文件中设置慢 SQL 阈值时间(永久生效):
#[mysqld]下配置:slow_query_log=1;slow_query_log_file=/var/lib/mysql/atguigu-slow.log
long_query_time=3;log_output=FILE;
日志分析工具 mysqldumpslow
在生产环境中,如果要手工分析日志,查找、分析 SQL,显然是个体力活,MySQL 提供了日志分析工具 mysqldumpslow。
查看 mysqldumpslow 的帮助信息,mysqldumpslow –help。
常用 mysqldumpslow 帮助信息:
s:是表示按照何种方式排序
c:访问次数
l:锁定时间
r:返回记录
t:查询时间
al:平均锁定时间
ar:平均返回记录数
at:平均查询时间
t:即为返回前面多少条的数据
g:后边搭配一个正则匹配模式,大小写不敏感的
工作常用参考:
得到返回记录集最多的 10 个 SQL:mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu-slow.log
得到访问次数最多的 10 个 SQL:mysqldumpslow -s c -t 10 /var/lib/mysql/atguigu-slow.log
得到按照时间排序的前 10 条里面含有左连接的查询语句:mysqldumpslow -s t -t 10 -g left join /var/lib/mysql/atguigu-slow.log
另外建议在使用这些命令时结合│和 more 使用,否则有可能出现爆屏情况:`mysqldumpslow -s r-t 10 /ar/lib/mysql/atguigu-slow.log | more
6.5 批量插入数据脚本
1、建表:
create database bigData;use bigData;// 部门表 CREATE TABLE dept(
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,
dname VARCHAR(20)NOT NULL DEFAULT ,
loc VARCHAR(13) NOT NULL DEFAULT )ENGINE=INNODB DEFAULT CHARSET=utf8;// 员工表 CREATE TABLE emp(
id int unsigned primary key auto_increment,
empno mediumint unsigned not null default 0, // 编号
ename varchar(20) not null default , // 名字
job varchar(9) not null default , // 工作
mgr mediumint unsigned not null default 0, // 上级编号
hiredate date not null, // 入职时间
sal decimal(7,2) not null, // 薪水
comm decimal(7,2) not null, // 红利
deptno mediumint unsigned not null default 0 // 部门编号)ENGINE=INNODB DEFAULT CHARSET=utf8;
2、设置参数 log_bin_trust_function_creators
创建函数,假如报错:This function has none of DETERMINISTIC…
由于开启过慢查询日志,因为我们开启了 bin-log,我们就必须为我们的 function 指定一个参数
show variables like log_bin_trust_function_creators set global log_bin_trust_function_creators=1;
这样添加了参数以后,如果 mysqld 重启,上述参数又会消失,永久方法:
windows 下:my.ini[mysqld] 加上 log_bin_trust_function_creators=1
linux 下:/etc/my.cnf 下 my.cnf[mysqld] 加上 log_bin_trust_function_creators=1
3、创建函数,保证每条数据都不同
随机产生字符串
delimiter $$ # 为了存储过程能正常运行,修改命令结束符,两个 $$ 表示结束 create function rand_string(n int) returns varchar(255)begin
declare chars_str varchar(100) default abcdefghijklmnopqrstuvwxyz
declare return_str varchar(255) default
declare i int default 0;
while i n do
set return_str = concat(return_str,substring(chars_str,floor(1+rand()*52),1));
set i=i+1;
end while;
return return_str;end $$
随机产生部门编号
delimiter $$create function rand_num() returns int(5)begin
declare i int default 0;
set i=floor(100+rand()*10);
return i;end $$
4、创建存储过程
创建往 emp 表中插入数据的存储过程
delimiter $$create procedure insert_emp(in start int(10),in max_num int(10)) #max_num:表示插入多少条数据 begin
declare i int default 0;
set autocommit = 0; # 关闭自动提交,避免写一个 insert 提交一次,50w 条一次性提交
repeat
set i = i+1;
insert into emp(empno,ename,job,mgr,hiredate,sal,comm,deptno) values((start+i),rand_string(6), salesman ,0001,curdate(),2000,400,rand_num());
until i=max_num end repeat;
commit;end $$
创建往 dept 表中插入数据的存储过程
delimiter $$create procedure insert_dept(in start int(10),in max_num int(10))begin
declare i int default 0;
set autocommit = 0;
repeat
set i = i+1;
insert into dept(deptno,dname,loc) values((start+i),rand_string(10),rand_string(8));
until i=max_num end repeat;
commit;end $$
5、调用存储过程
往 dept 表中插入数据
mysql DELIMITER ; # 修改默认结束符号为(;),之前改成了 ##
mysql CALL insert_dept(100, 10);
Query OK, 0 rows affected (0.01 sec)
往 emp 表中插入 50 万数据
mysql DELIMITER ;
mysql CALL insert_emp(100001, 500000);
Query OK, 0 rows affected (27.00 sec)
查看运行结果
mysql select * from dept;
+----+--------+---------+--------+
| id | deptno | dname | loc |
+----+--------+---------+--------+
| 1 | 101 | mqgfy | ck |
| 2 | 102 | wgighsr | kbq |
| 3 | 103 | gjgdyj | brb |
| 4 | 104 | gzfug | p |
| 5 | 105 | keitu | cib |
| 6 | 106 | nndvuv | csue |
| 7 | 107 | cdudl | tw |
| 8 | 108 | aafyea | aqq |
| 9 | 109 | zuqezjx | dpqoyo |
| 10 | 110 | pam | cses |
+----+--------+---------+--------+
10 rows in set (0.00 sec)
mysql select * from emp limit 10; # 查看前 10 条数据(50W 太多了)+----+--------+-------+----------+-----+------------+---------+--------+--------+
| id | empno | ename | job | mgr | hiredate | sal | comm | deptno |
+----+--------+-------+----------+-----+------------+---------+--------+--------+
| 1 | 100002 | xmbva | salesman | 1 | 2021-04-05 | 2000.00 | 400.00 | 108 |
| 2 | 100003 | aeq | salesman | 1 | 2021-04-05 | 2000.00 | 400.00 | 109 |
| 3 | 100004 | cnjfz | salesman | 1 | 2021-04-05 | 2000.00 | 400.00 | 105 |
| 4 | 100005 | wwhd | salesman | 1 | 2021-04-05 | 2000.00 | 400.00 | 100 |
| 5 | 100006 | e | salesman | 1 | 2021-04-05 | 2000.00 | 400.00 | 107 |
| 6 | 100007 | yjfr | salesman | 1 | 2021-04-05 | 2000.00 | 400.00 | 108 |
| 7 | 100008 | xlp | salesman | 1 | 2021-04-05 | 2000.00 | 400.00 | 102 |
| 8 | 100009 | mp | salesman | 1 | 2021-04-05 | 2000.00 | 400.00 | 102 |
| 9 | 100010 | tcdl | salesman | 1 | 2021-04-05 | 2000.00 | 400.00 | 107 |
| 10 | 100011 | akw | salesman | 1 | 2021-04-05 | 2000.00 | 400.00 | 106 |
+----+--------+-------+----------+-----+------------+---------+--------+--------+
10 rows in set (0.00 sec)
6.6 Show Profile 进行 sql 分析(重中之重)
Show Profile 是 mysql 提供可以用来分析当前会话中语句执行的资源消耗情况。可以用于 SQL 的调优的测量
官网文档
默认情况下,参数处于关闭状态,并保存最近 15 次的运行结果
分析步骤:
1、是否支持,看看当前的 mysql 版本是否支持:show variables like profiling
默认是关闭,使用前需要开启
2、开启功能,默认是关闭,使用前需要开启:set profiling=on;
3、运行 SQL(随便运行用来测试)
mysql select * from emp group by id%10 limit 150000;
mysql select * from emp group by id%20 order by 5;
4、查看结果:show profiles;
mysql show profiles;
+----------+------------+-----------------------------------------------+
| Query_ID | Duration | Query |
+----------+------------+-----------------------------------------------+
| 1 | 0.00204000 | show variables like profiling |
| 2 | 0.55134250 | select * from emp group by id%10 limit 150000 |
| 3 | 0.56902000 | select * from emp group by id%20 order by 5 |
+----------+------------+-----------------------------------------------+
3 rows in set, 1 warning (0.00 sec)
5、诊断 SQL,show profile cpu,block io for query ID 号;(ID 号为第 4 步 Query_ID 列中数字)
mysql show profile cpu,block io for query 3;
+----------------------+----------+----------+------------+--------------+---------------+
| Status | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out |
+----------------------+----------+----------+------------+--------------+---------------+
| starting | 0.000049 | 0.000000 | 0.000000 | NULL | NULL |
| checking permissions | 0.000005 | 0.000000 | 0.000000 | NULL | NULL |
| Opening tables | 0.000012 | 0.000000 | 0.000000 | NULL | NULL |
| init | 0.000021 | 0.000000 | 0.000000 | NULL | NULL |
| System lock | 0.000009 | 0.000000 | 0.000000 | NULL | NULL |
| optimizing | 0.000003 | 0.000000 | 0.000000 | NULL | NULL |
| statistics | 0.000017 | 0.000000 | 0.000000 | NULL | NULL |
| preparing | 0.000008 | 0.000000 | 0.000000 | NULL | NULL |
| Creating tmp table | 0.000045 | 0.000000 | 0.000000 | NULL | NULL |
| Sorting result | 0.000004 | 0.000000 | 0.000000 | NULL | NULL |
| executing | 0.000002 | 0.000000 | 0.000000 | NULL | NULL |
| Sending data | 0.568704 | 0.546875 | 0.046875 | NULL | NULL |
| Creating sort index | 0.000048 | 0.000000 | 0.000000 | NULL | NULL |
| end | 0.000003 | 0.000000 | 0.000000 | NULL | NULL |
| query end | 0.000005 | 0.000000 | 0.000000 | NULL | NULL |
| removing tmp table | 0.000006 | 0.000000 | 0.000000 | NULL | NULL |
| query end | 0.000003 | 0.000000 | 0.000000 | NULL | NULL |
| closing tables | 0.000004 | 0.000000 | 0.000000 | NULL | NULL |
| freeing items | 0.000061 | 0.000000 | 0.000000 | NULL | NULL |
| cleaning up | 0.000015 | 0.000000 | 0.000000 | NULL | NULL |
+----------------------+----------+----------+------------+--------------+---------------+
20 rows in set, 1 warning (0.00 sec)
参数备注(写在代码中):show profile cpu,block io for query 3;(如此代码中的 cpu,block)
ALL:显示所有的开销信息。
BLOCK IO:显示块 lO 相关开销。
CONTEXT SWITCHES:上下文切换相关开销。
CPU:显示 CPU 相关开销信息。
IPC:显示发送和接收相关开销信息。
MEMORY:显示内存相关开销信息。
PAGE FAULTS:显示页面错误相关开销信息。
SOURCE:显示和 Source_function,Source_file,Source_line 相关的开销信息。
SWAPS:显示交换次数相关开销的信息。
6、日常开发需要注意的结论(Status 列中的出现此四个问题严重)
converting HEAP to MyISAM:查询结果太大,内存都不够用了往磁盘上搬了。
Creating tmp table:创建临时表,拷贝数据到临时表,用完再删除
Copying to tmp table on disk:把内存中临时表复制到磁盘,危险!
locked:锁了
6.7 全局查询日志
永远不要在生产环境开启这个功能,只能在测试环境使用!
第一种:配置文件启用。在 mysq l 的 my.cnf 中,设置如下:
#开启 general_log=1#记录日志文件的路径 general_log_file=/path/logfile# 输出格式 log_output=FILE
第二种:编码启用。命令如下:
set global general_log=1;
set global log_output= TABLE
此后,你所编写的 sql 语句,将会记录到 mysql 库里的 geneial_log 表,可以用下面的命令查看:
mysql select * from mysql.general_log;
+----------------------------+------------------------------+-----------+-----------+--------------+---------------------------------+
| event_time | user_host | thread_id | server_id | command_type | argument |
+----------------------------+------------------------------+-----------+-----------+--------------+---------------------------------+
| 2021-04-05 19:57:28.182473 | root[root] @ localhost [::1] | 5 | 1 | Query | select * from mysql.general_log |
+----------------------------+------------------------------+-----------+-----------+--------------+---------------------------------+
1 row in set (0.00 sec)
7 MySQL 锁机制 7.1 概述
定义:
锁是计算机协调多个进程或线程并发访问某一资源的机制。
在数据库中,除传统的计算资源(如 CPU、RAM、I/ O 等)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂
例子:京东购物
打个比方,我们到京东上买一件商品,商品只有一件库存,这个时候如果还有另一个人买,那么如何解决是你买到还是另一个人买到的问题?
这里肯定要用到事务,我们先从库存表中取出物品数量,然后插入订单,付款后插入付款表信息,然后更新商品数量。在这个过程中,使用锁可以对有限的资源进行保护,解决隔离和并发的矛盾
锁的分类:
从对数据操作的类型(读 \ 写)分
读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响。
写锁(排它锁):当前写操作没有完成前,它会阻断其他写锁和读锁。
从对数据操作的粒度分
表锁
行锁
7.2 表锁(偏读)
特点:偏向 MyISAM 存储引擎,开销小,加锁快;无死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
读锁案例讲解 1
案例分析
建表表
create table mylock (
id int not null primary key auto_increment,
name varchar(20) default
) engine myisam;
insert into mylock(name) values( a
insert into mylock(name) values( b
insert into mylock(name) values( c
insert into mylock(name) values( d
insert into mylock(name) values( e
mysql select * from mylock;
+----+------+
| id | name |
+----+------+
| 1 | a |
| 2 | b |
| 3 | c |
| 4 | d |
| 5 | e |
+----+------+
5 rows in set (0.00 sec)
手动增加表锁:lock table 表名字 read(write), 表名字 2 read(write), 其他;
mysql lock table mylock read;Query OK, 0 rows affected (0.00 sec)
查看表上加过的锁:show open tables;
mysql show open tables;
+--------------------+------------------------------------------------------+--------+-------------+
| Database | Table | In_use | Name_locked |
+--------------------+------------------------------------------------------+--------+-------------+
| performance_schema | events_waits_summary_by_user_by_event_name | 0 | 0 |
| performance_schema | events_waits_summary_global_by_event_name | 0 | 0 |
| performance_schema | events_transactions_summary_global_by_event_name | 0 | 0 |
| performance_schema | replication_connection_status | 0 | 0 |
| mysql | time_zone_leap_second | 0 | 0 |
| mysql | columns_priv | 0 | 0 |
| my | test03 | 0 | 0 |
| bigdata | mylock | 1 | 0 |
# In_use 为 1 时表示已上锁
释放锁:unlock tables;
mysql unlock tables;
Query OK, 0 rows affected (0.00 sec)
# 再次查看
mysql show open tables;
+--------------------+------------------------------------------------------+--------+-------------+
| Database | Table | In_use | Name_locked |
+--------------------+------------------------------------------------------+--------+-------------+
| performance_schema | events_waits_summary_by_user_by_event_name | 0 | 0 |
| performance_schema | events_waits_summary_global_by_event_name | 0 | 0 |
| performance_schema | events_transactions_summary_global_by_event_name | 0 | 0 |
| performance_schema | replication_connection_status | 0 | 0 |
| mysql | time_zone_leap_second | 0 | 0 |
| mysql | columns_priv | 0 | 0 |
| my | test03 | 0 | 0 |
| bigdata | mylock | 0 | 0 |
加读锁——为 mylock 表加 read 锁(读阻塞写例子)
读锁案例讲解 2
为 mylock 表加 write 锁(MylSAM 存储引擎的写阻塞读例子)
MyISAM 在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给涉及的表加写锁。
MySQL 的表级锁有两种模式:
表共享读锁(Table Read Lock)
表独占写锁(Table Write Lock)
结合上表,所以对 MyISAM 表进行操作,会有以下情况:
对 MyISAM 表的读操作(加读锁),不会阻塞其他进程对同一表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,才会执行其它进程的写操作。
对 MyISAM 表的写操作〈加写锁),会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其它进程的读写操作。
重点!:简而言之,就是读锁会阻塞写,但是不会堵塞读。而写锁则会把读和写都堵塞
表锁总结
看看哪些表被加锁了:show open tables;
如何分析表锁定
可以通过检查 table_locks_waited 和 table_locks_immediate 状态变量来分析系统上的表锁定
mysql show status like table_locks%
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Table_locks_immediate | 170 |
| Table_locks_waited | 0 |
+-----------------------+-------+
2 rows in set (0.00 sec)
这里有两个状态变量记录 MySQL 内部表级锁定的情况,两个变量说明如下:
Table_locks_immediate:产生表级锁定的次数,表示可以立即获取锁的查询次数,每立即获取锁值加 1 ;
Table_locks_waited(重点):出现表级锁定争用而发生等待的次数(不能立即获取锁的次数,每等待一次锁值加 1),此值高则说明存在着较严重的表级锁争用情况;
此外,MyISAM 的读写锁调度是写优先,这也是 MyISAM 不适合做写为主表的引擎。因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞
7.3 行锁(偏写)
偏向 InnoDB 存储引擎,开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
InnoDB 与 MyISAM 的最大不同有两点:一是支持事务(TRANSACTION);二是采用了行级锁
由于行锁支持事务,复习老知识:
事务(Transaction)及其 ACID 属性
并发事务处理带来的问题
事务隔离级别
1)事务是由一组 SQL 语句组成的逻辑处理单元,事务具有以下 4 个属性,通常简称为事务的 ACID 属性:
原子性(Atomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性; 事务结束时,所有的内部数据结构〈如 B 树索引或双向链表)也都必须是正确的。
隔离性(lsolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。
2)并发事务处理带来的问题
更新丢失(Lost Update)
当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题――最后的更新覆盖了由其他事务所做的更新。
例如,两个程序员修改同一 java 文件。每程序员独立地更改其副本,然后保存更改后的副本,这样就覆盖了原始文档。最后保存其更改副本的编辑人员覆盖前一个程序员所做的更改。
如果在一个程序员完成并提交事务之前,另一个程序员不能访问同一文件,则可避免此问题。
脏读(Dirty Reads)
一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做”脏读”。
一句话:事务 A 读取到了事务 B 已修改但尚未提交的的数据,还在这个数据基础上做了操作。此时,如果 B 事务回滚,A 读取的数据无效,不符合一致性要求
不可重复读(Non-Repeatable Reads)
一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了,这种现象就叫做“不可重复读”。
一句话:事务 A 读取到了事务 B 已经提交的修改数据,不符合隔离性。
幻读(Phantom Reads)
一个事务接相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读“。
一句话:事务 A 读取到了事务 B 体提交的新增数据,不符合隔离性
多说一句:幻读和脏读有点类似。脏读是事务 B 里面修改了数据;幻读是事务 B 里面新增了数据。
3)事务隔离级别
”脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决
数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的。同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关心数据并发访问的能力。
常看当前数据库的事务隔离级别:show variables like tx_isolation
mysql show variables like tx_isolation
+---------------+-----------------+
| Variable_name | Value |
+---------------+-----------------+
| tx_isolation | REPEATABLE-READ |
+---------------+-----------------+
1 row in set, 1 warning (0.00 sec)
# 默认情况下:MySQL 避免了脏读和不可重复读
行锁案例讲解
建表:
CREATE TABLE test_innodb_lock (a INT(11),b VARCHAR(16))ENGINE=INNODB;
INSERT INTO test_innodb_lock VALUES(1, b2
INSERT INTO test_innodb_lock VALUES(3, 3
INSERT INTO test_innodb_lock VALUES(4, 4000
INSERT INTO test_innodb_lock VALUES(5, 5000
INSERT INTO test_innodb_lock VALUES(6, 6000
INSERT INTO test_innodb_lock VALUES(7, 7000
INSERT INTO test_innodb_lock VALUES(8, 8000
INSERT INTO test_innodb_lock VALUES(9, 9000
INSERT INTO test_innodb_lock VALUES(1, b1
CREATE INDEX test_innodb_a_ind ON test_innodb_lock(a);
CREATE INDEX test_innodb_lock_b_ind ON test_innodb_lock(b);
mysql select * from test_innodb_lock;
+------+------+
| a | b |
+------+------+
| 1 | b2 |
| 3 | 3 |
| 4 | 4000 |
| 5 | 5000 |
| 6 | 6000 |
| 7 | 7000 |
| 8 | 8000 |
| 9 | 9000 |
| 1 | b1 |
+------+------+
9 rows in set (0.00 sec)
mysql show index from test_innodb_lock;
+------------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+------------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| test_innodb_lock | 1 | test_innodb_a_ind | 1 | a | A | 8 | NULL | NULL | YES | BTREE | | |
| test_innodb_lock | 1 | test_innodb_lock_b_ind | 1 | b | A | 9 | NULL | NULL | YES | BTREE | | |
+------------------+------------+------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
行锁定基本演示(两个客户端更新同一行记录)
疑惑解答为什么两个都要 commint
索引失效行锁变表锁
无索引行锁升级为表锁
间隙锁
什么是间隙锁
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB 会给符合条件的已有数据记录的索引项加锁,对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”。
InnoDB 也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key 锁)。
危害
因为 Query 执行过程中通过过范围查找的话,他会锁定整个范围内所有的索引键值,即使这个键值并不存在。
间隙锁有一个比较致命的弱点,就是当锁定一个范围键值之后,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能造成很大的危害
面试题:如何锁定一行
begin(中间写自己的操作)commit
行锁总结
总结:
Innodb 存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更高一些,但是在整体并发处理能力方面要远远优于 MyISAM 的表级锁定的。当系统并发量较高的时候,Innodb 的整体性能和 MylISAM 相比就会有比较明显的优势了。
但是,Innodb 的行级锁定同样也有其脆弱的一面,当我们使用不当的时候,可能会让 Innodb 的整体性能表现不仅不能比 MyISAM 高,甚至可能会更差
如何分析行锁定?
通过检查 lnnoDB_row_lock 状态变量来分析系统上的行锁的争夺情况:show status like innodb_row_lock%
mysql show status like innodb_row_lock%
+-------------------------------+-------+
| Variable_name | Value |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 0 |
| Innodb_row_lock_time | 0 |
| Innodb_row_lock_time_avg | 0 |
| Innodb_row_lock_time_max | 0 |
| Innodb_row_lock_waits | 0 |
+-------------------------------+-------+
5 rows in set (0.00 sec)
对各个状态量的说明如下:
Innodb_row_lock_current_waits:当前正在等待锁定的数量;
Innodb_row_lock_time:从系统启动到现在锁定总时间长度;
Innodb_row_lock_time_avg:每次等待所花平均时间;
Innodb_row_lock_time_max:从系统启动到现在等待最常的一次所花的时间;
Innodb_row_lock_waits:系统启动后到现在总共等待的次数;
对于这 5 个状态变量,比较重要的主要是:
lnnodb_row_lock_time(等待总时长)
Innodb_row_lock_time_avg(等待平均时长)
lnnodb_row_lock_waits(等待总次数)
尤其是当等待次数很高,而且每次等待时长也不小的时候,我们就需要分析(Show Profile)系统中为什么会有如此多的等待,然后根据分析结果着手指定优化计划。
优化建议
尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁。
合理设计索引,尽量缩小锁的范围
尽可能较少检索条件,避免间隙锁
尽量控制事务大小,减少锁定资源量和时间长度
尽可能低级别事务隔离
页锁
开销和加锁时间界于表锁和行锁之间; 会出现死锁; 锁定粒度界于表锁和行锁之间,并发度一般。(了解一下即可)
8 主从复制 8.1 复制的基本原理
slave 会从 master 读取 binlog 来进行数据同步
原理图:
MySQL 复制过程分成三步:
1、master 将改变记录到二进制日志(binary log)。这些记录过程叫做二进制日志事件,binary log events;
2、slave 将 master 的 binary log events 拷贝到它的中继日志(relay log) ;
3、slave 重做中继日志中的事件,将改变应用到自己的数据库中。MySQL 复制是异步的且串行化的
8.2 复制的基本原则
每个 slave 只有一个 master
每个 slave 只能有一个唯一的服务器 ID
每个 master 可以有多个 salve
复制的最大问题是延迟。
8.3 一主一从常见配置
一、mysql 版本一致且后台以服务运行
二、主从都配置在 [mysqld] 结点下,都是小写
主机修改 my.ini 配置文件:
1、[必须]主服务器唯一 ID:server-id=1
2、[必须]启用二进制日志
log-bin= 自己本地的路径 /mysqlbin
log-bin=D:/devSoft/MySQLServer5.5/data/mysqlbin
3、[可选]启用错误日志
log-err= 自己本地的路径 /mysqlerr
log-err=D:/devSoft/MySQLServer5.5/data/mysqlerr
4、[可选]根目录
basedir=“自己本地路径”
basedir=“D:/devSoft/MySQLServer5.5/”
5、[可选]临时目录
tmpdir=“自己本地路径”
tmpdir=“D:/devSoft/MySQLServer5.5/”
6、[可选]数据目录
datadir=“自己本地路径 /Data/”
datadir=“D:/devSoft/MySQLServer5.5/Data/”
7、主机,读写都可以
read-only=O
8、[可选]设置不要复制的数据库
binlog-ignore-db=mysql
9、[可选]设置需要复制的数据库
binlog-do-db= 需要复制的主数据库名字
从机修改 my.cnf 配置文件:
1、[必须]从服务器唯一 ID:vim etc/my.cnf(进入修改配置文件)
...#server-id=1 // 注释吊...server-id=1 // 开启...
2、[可选]启用二进制日志
三、配置文件,请主机 + 从机都重启后台 mysql 服务
主机:手动重启
Linux 从机命名:
service mysql stop
service mysql start
四、主机从机都关闭防火墙
windows 手动关闭
关闭虚拟机 linux 防火墙:service iptables stop
五、在 Windows 主机上建立帐户并授权 slave
GRANT REPLICATION SLAVE ON . TO‘zhangsan’@‘从机器数据库 IP’IDENTIFIED BY‘123456’;
刷新:flush privileges;
查询 master 的状态
show master status;
记录下 File 和 Position 的值
执行完此步骤后不要再操作主服务器 MYSQL,防止主服务器状态值变化
六、在 Linux 从机上配置需要复制的主机
CHANGE MASTER TO MASTER_HOST=’主机 IP’,
MASTER_USER=‘zhangsan’,
MASTER_PASSWORD=’123456’,
MASTER_LOG_FILE= File 名字’,
MASTER_LOG_POS=Position 数字;
启动从服务器复制功能:start slave;
show slave status\G(下面两个参数都是 Yes,则说明主从配置成功!)
Slave_IO_Running:Yes
Slave_SQL_Running:Yes
七、主机新建库、新建表、insert 记录,从机复制
主机操作
从机(自动同步)
八、如何停止从服务复制功能:stop slave;
如果有一段数据暂时不要?
从机:
主机(需要重新查刻度):
读到这里,这篇“MySQL 的 SQL 优化、索引优化、锁机制、主从复制知识有哪些”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注丸趣 TV 行业资讯频道。