MySQL进阶之MySQL索引以及索引优化
本文配合B站学习视频BV1es411u7we使用效果更佳。
1. MySQL版本
主流版本:5.x版
5.0 - 5.1:早期产品的延续,升级维护
5.4 - 5.x:MySQL整合了三方公司的新存储引擎(5.5)
安装:rpm -ivh xxx
或tar -zxvf xxx.tar.gz
查看已有的相关文件:
rpm -qa | grep xxx
安装过程中出现冲突时需将冲突的软件卸载掉:
yum -y remove xxx
或rpm -e --nodeps xxx
验证:
mysqladmin --version
服务:
启动服务:
service mysql start
关闭服务:
service mysql stop
重启服务:
service mysql restart
服务开机自启/关闭:
chkconfig mysql on/off
登录:mysql
设置初始密码:
/usr/bin/mysqladmin -u root password 'new-password'
授权远程连接:
授权:
grant all privileges on *.* to '用户名' @'%' identified by '密码';
刷新权限:
flush privileges;
开启防火墙服务:
systemctl start firewalld.service
开启3306端口:
firewall-cmd --zone = public --query-port = 3306/tcp
重新加载防火墙服务:
firewall-cmd --reload
数据库存放目录:ps -ef|grep mysql
数据存放目录:
datadir=/var/lib/mysql
pid文件目录:
pid-file=/var/lib/mysql/chiaki01.pid
进入目录
cd /var/lib/mysql
,其中mysql
和mysql.sock
比较重要
MySQL核心目录:
MySQL安装目录:
/var/lib/mysql
MySQL配置文件:
/usr/share/mysql
中的``my-huge.cnf、
my-large.cnf`等MySQL命令目录:
/usr/bin
,包含mysqladmin
、musqldump
等命令MySQL启停脚本:
/etc/init.d/mysql
MySQL配置文件目录:
/etc/my.cnf
,不存在就复制过来cp /usr/share/mysql/my-huge.cnf /etc/my.cnf
MySQL编码查询:show variables like '%char%';
统一编码为utf8:进入配置文件my.cnf
进行修改
[mysql]:
default-character-set=utf8
[client]:
default-character-set=utf8
[server]:
character_set_server=utf8
,character_set_client=utf8
,collation_server=utf8_general_ci
注意事项:修改编码只对修改之后的创建的数据库生效
MySQL清屏:system clear
, ctrl+L
备注:5.5以上安装的一些命令
查看初始密码:
cat /root/.mysql_sercet
安装完初始登录:
mysql -uroot -p
并输入密码登录成功设置密码安全策略并修改密码(5.5以上):
- 改变密码等级:
set global validate_password_policy=0;
- 改变密码最小长度:
set global validate_password_length=4;
- 修改密码:
SET PASSWORD = PASSWORD('密码');
授权:
grant all privileges on *.* to '用户名' @'%' identified by '密码';
刷新权限:
flush privileges;
开放远程连接(关闭防火墙服务或者开放防火墙3306端口)
- 关闭防火墙服务:
systemctl stop firewalld.service
- 开启防火墙服务:
systemctl start firewalld.service
- 开启3306端口:
firewall-cmd --zone = public --query-port = 3306/tcp
- 重新加载防火墙服务:
firewall-cmd --reload
- 查看服务:
firewall-cmd --list-all
CentOS7安装MySQL5.7:https://www.cnblogs.com/Mr-Rshare/p/11799945.html
2. MySQL底层原理
逻辑分层(自顶向下)
连接层:提供与客户端连接的服务
服务层:提供各种用户使用的接口(select等);提供SQL优化器(MySQL Query Optimizer)
引擎层:提供了各种存储数据的方式(InnoDB和MyISAM等)
存储层:存储数据
引擎区别
InnoDB:事务优先(适合高并发操作;行锁)(5.5及以上默认引擎)
MyISAM:性能优先(不支持事务;表锁)
引擎相关SQL语句
查询数据库支持的引擎:
show engines
查询当前使用的引擎:
show variables like '%storage_engine%';
创建数据库对象时指定引擎:
CREATE TABLE tb (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR (5),
dept VARCHAR (5)
) ENGINE = MYISAM AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8 ;
指定数据库对象的引擎:
show table status like "tb" ;
3. SQL优化
为什么要SQL优化
性能低
执行时间长
等待时间长
SQL语句欠佳(连接查询)
索引失效
服务器参数设置不合理(缓冲、线程)
SQL解析
编写过程:select dinstinct...from...join...on...where...group by...having...order by...limit...
解析过程:from...on...join...where...group by...having...select dinstinct...order by...limit...
SQL优化之索引简介
索引(Index):是帮助MySQL高效获取数据的数据结构(B树(MySQL默认)和哈希索引)。
B树中的2-3树:3层B树可以存放上百万条数据
B+树:B树的一种,是MySQL使用的索引结构,数据全部存放在叶节点中。B+树中给查询任意的数据次数为n次,即B+树的高度。
索引的弊端:
- 索引本身很大,可以存放在内存/硬盘(通常为硬盘)。
- 索引不是所有情况均适用:少量数据;频繁更新的字段;很少使用的字段。
- 索引确实可以提高查询效率,但是会降低增删改的效率。
索引的优势:
- 提高查询效率(降低IO使用率)
- 降低CPU使用率(...order by age desc,因为B树索引已经是一个排好序的结构)
4. 索引
索引分类
单值索引:单列,一个表可以有多个单值索引
唯一索引:在单值索引基础上,字段的值不可重复,一般为id
复合索引:多个列构成的索引(相当于二级目录)
索引创建
创建索引(一):create 索引类型 索引名 on 表名(字段)
单值索引:
create index dept_index on tb(dept);
唯一索引:
create unique index name_index on tb(name);
复合索引:
create index dept_name_index on tb(dept,name);
创建索引(二):alter table 表名 add 索引类型 索引名(字段)
单值索引:
alter table tb add index dept_index(dept);
唯一索引:
alter table tb add unique index name_index(name);
复合索引:
alter table tb add index dept_name_index(dept,name);
注意事项
如果一个字段设置为主键,则该字段默认就是主键索引,与唯一索引较类似,但存在区别。
主键索引不能是null,唯一索引可以为null。
删除及查询索引
删除索引:
drop index 索引名 on 表名
查询索引:
show index from 表名
5. SQL性能问题及优化
分析SQL的执行计划:explain
,模拟SQL优化器执行SQL语句,使开发人员清除编写的SQL状况。SQL优化器会干扰优化。
Explian查询执行计划
查询执行计划:explain + SQL语句
explain select * from tb;
- id:编号
- select_type:查询类型
- table:表
- type:类型
- possible_keys:预测用到的索引
- key:实际使用的索引
- ken_len:实际使用索引的长度
- ref:表之间的引用关系
- rows:通过索引查询到的数据记录数
- Extra:额外信息
案例:
建表并插入记录:
USE myDB; -- 试验中先不设置主键和外键
CREATE TABLE teacherCard (
tcid INT,
tcdesc VARCHAR (30)
) ; CREATE TABLE teacher (
tid INT ,
tname VARCHAR (20),
tcid INT
) ; CREATE TABLE course (
cid INT ,
cname VARCHAR (20),
tid INT
) ; INSERT INTO teacherCard VALUES(1,'tzdesc');
INSERT INTO teacherCard VALUES(2,'twdesc');
INSERT INTO teacherCard VALUES(3,'tldesc'); INSERT INTO teacher VALUES(1,'tz',1);
INSERT INTO teacher VALUES(2,'tw',2);
INSERT INTO teacher VALUES(3,'tl',3); INSERT INTO course VALUES(1,'java',1);
INSERT INTO course VALUES(2,'html',1);
INSERT INTO course VALUES(3,'sql',2);
INSERT INTO course VALUES(4,'web',3);
explain + SQL语句:
练习:查询课程编号为2或教师证编号为3的老师信息
EXPLAIN
SELECT
t.*
FROM
teacher t,
course c,
teacherCard tc
WHERE t.tid = c.tid
AND t.tcid = tc.tcid
AND (c.cid = 2
OR tc.tcid = 3) ;
id:编号
- id值相同,从上往下顺序执行。
执行顺序
t(3)-tc(3)-c(4)
(括号中表示表中的记录数)。现向teacher标值再插入3条数据,并执行同样的SQL语句。INSERT INTO teacher VALUES(4,'ta',4);
INSERT INTO teacher VALUES(5,'tb',5);
INSERT INTO teacher VALUES(6,'tc',6); EXPLAIN
SELECT
t.*
FROM
teacher t,
course c,
teacherCard tc
WHERE t.tid = c.tid
AND t.tcid = tc.tcid
AND (c.cid = 2
OR tc.tcid = 3) ;
上图结果中,执行顺序变为:
tc(3)-c(4)-t(6)
表的执行顺序因表中记录数的改变而改变,其原因在于:笛卡尔积。记录数最小的表优先查询,使中间笛卡尔积最小。
验证:删除course表中的两条记录,再次执行查看结果:
DELETE FROM course WHERE cid > 2; EXPLAIN
SELECT
t.*
FROM
teacher t,
course c,
teacherCard tc
WHERE t.tid = c.tid
AND t.tcid = tc.tcid
AND (c.cid = 2
OR tc.tcid = 3) ;
上图结果中,执行顺序变为:
c(2)-tc(3)-t(6)
- id值不同,id值越大的越优先执行(本质:在嵌套子查询时,先查内层,在查外层)。
练习:查询教授SQL课程的老师的描述。
EXPLAIN
SELECT
tc.tcdesc
FROM
teacherCard tc,
course c,
teacher t
WHERE t.tid = c.tid
AND t.tcid = tc.tcid
AND c.cname = 'SQL' ;
将以上多表查询转为子查询形式
EXPLAIN
SELECT
tc.tcdesc
FROM
teacherCard tc
WHERE tcid =
(SELECT
t.tcid
FROM
teacher t
WHERE t.tid =
(SELECT
c.tid
FROM
course c
WHERE c.cname = 'SQL')) ;
id值越大的先执行,执行顺序为:
c(2)-t(6)-tc(3)
- id值有相同有不同:id值越大越优先,如果id值相同,从上往下依次执行。
综合:子查询+多表:查询教授SQL课程的老师的描述。
EXPLAIN
SELECT
t.tname,
tc.tcdesc
FROM
teacher t,
teacherCard tc
WHERE t.tcid = tc.tcid
AND t.tid =
(SELECT
c.cid
FROM
course c
WHERE c.cname = 'SQL') ;
上图结果中,执行顺序变为:
c(2)-tc(3)-t(6)
select_type:查询类型
PRIMARY
:包含子查询SQL中的主查询(最外层)
SUBQUERY
:包含子查询SQL中的子查询(非最外层)
SIMPLE
:简单查询(不包含子查询和union连接查询)EXPLAIN SELECT * FROM teacher t;
DERIVED
:衍生查询(使用到了临时表)
UNION
:见下例
UNION RESULT
:告知开发者哪些表存在UNION查询,见下例
在FROM子查询中只有一张表:查询教课老师编号是1或2的课程信息。
EXPLAIN
SELECT
cr.cname
FROM
(SELECT
*
FROM
course
WHERE tid IN (1, 2)) cr ;
在FROM子查询中,如果table1 union table2,则table1(左表)就是DERIVED,而table2就是UNION:查询教课老师编号是1或2的课程信息。
EXPLAIN
SELECT
cr.cname
FROM
(SELECT
*
FROM
course
WHERE tid = 1
UNION
SELECT
*
FROM
course
WHERE tid = 2) cr ;
type:索引类型、类型
常用type:
system > const > eq_ref > ref > range > index > all,性能依次降低,其中system和const只是理想情况,实际能达到最高为ref。要对type进行优化的前提是要有索引。
system
:只有一条数据的系统表或衍生表只有一条数据的主查询。-- 创建test01表
CREATE TABLE test01 (tid INT, tname VARCHAR (20));
-- test01表中插入1条数据
INSERT INTO test01 VALUES(1,'a');
-- 添加主键索引(优化type的前提)
ALTER TABLE test01 ADD CONSTRAINT tid_pk PRIMARY KEY(tid);
-- 分析执行计划(衍生表只有1条数据)
EXPLAIN SELECT * FROM (SELECT * FROM test01) t WHERE t.tid = 1;
上图中衍生表的type为system。
const
:仅能查到一条数据的SQL,用于Primary key或Unique索引(与索引类型有关)EXPLAIN SELECT t.tid FROM test01 t WHERE t.tid = 1;
上图中由于仅能查到1条数据,同时用于主键索引,因此type为const。
-- 删除主键索引
ALTER TABLE test01 DROP PRIMARY KEY;
-- 添加单值索引
ALTER TABLE test01 ADD INDEX test01_index(tid);
-- 再次分析执行计划
EXPLAIN SELECT t.tid FROM test01 t WHERE t.tid = 1;
上图中仅能查到1条数据,但用于一般单值索引,因此type不是const。
eq_ref
:唯一性索引,即对于每个索引键的查询,返回唯一匹配行数据(有且仅有一个),常见于唯一索引和主键索引。-- teacherCard表设置主键
ALTER TABLE teacherCard ADD CONSTRAINT tcid_pk PRIMARY KEY(tcid);
-- teacher表设置唯一键约束
ALTER TABLE teacher ADD CONSTRAINT uk_tcid UNIQUE INDEX(tcid);
-- 连接查询
SELECT t.tcid FROM teacher t, teacherCard tc WHERE t.tcid = tc.tcid;
-- 分析执行计划
EXPLAIN SELECT t.tcid FROM teacher t, teacherCard tc WHERE t.tcid = tc.tcid;
-- 查询teacher表
SELECT * FROM teacher;
上图中type的结果不是eq_ref,其原因在于不满足有且仅有一个,因为在teacher表中的tid唯一索引的返回的结果有6条,而连接查询返回的结果只有3条,所以不满足条件。
删除后teacher表的后三条数据再次分析执行计划:
-- 删除后三条数据
DELETE FROM teacher WHERE tid > 3;
-- 分析执行计划
EXPLAIN SELECT t.tcid FROM teacher t, teacherCard tc WHERE t.tcid = tc.tcid;
上图结果中type为eq_ref。以上SQL,用到的索引是t.tcid,即teacher表中的tcid字段。如果teacher表的数据个数和链接连接查询的数据个数一直,才能满足eq_ref级别。
ref
:非唯一性索引:对于每隔索引键的索引,返回匹配的所有行。-- 数据准备使得teacher表中tname列中存在重复tz
INSERT INTO teacher VALUES(4,'tz',4);
INSERT INTO teacherCard VALUES(4,'tz2222');
-- 创建teacher表tname列的索引
ALTER TABLE teacher ADD INDEX tname_index(tname);
-- 使用tname作为索引进行查询
SELECT * FROM teacher t WHERE t.tname = 'tz';
-- 分析执行计划
EXPLAIN SELECT * FROM teacher t WHERE t.tname = 'tz';
上图的结果中type为ref。
range
:检索指定范围的行,where后是一个范围查询(between...and...,>,< 等),其中范围查询使用in时,有可能失效转为无索引all。-- teacher表的tid列添加索引
ALTER TABLE teacher ADD INDEX tid_index(tid);
-- 分析执行计划
EXPLAIN SELECT t.* FROM teacher t WHERE t.tid IN (1, 2); -- 失效 变为all
EXPLAIN SELECT t.* FROM teacher t WHERE t.tid < 3; -- range
EXPLAIN SELECT t.* FROM teacher t WHERE t.tid > 3; -- range
EXPLAIN SELECT t.* FROM teacher t WHERE t.tid BETWEEN 1 AND 2; -- range
index
:查询全部索引中的数据-- 查询teacher表中tid列的所有数据(确保tid列已有索引) 只需扫描索引表
EXPLAIN SELECT t.tid FROM teacher t; -- type为index
all
:查询全部表中的数据-- course表没有索引 需要全表扫描
EXPLAIN SELECT c.cid FROM course c; -- type为all
总结:
- system/const:结果只有一条数据。
- eq_ref:结果多条,但每条数据有且仅有一条(不能为0也不能为多)。
- ref:结果多条名单每条数据是0或多(唯一则为eq_ref)。
possible_keys:可能用的索引
key:实际用的索引
possible_keys是一种预测,不准。如果possible_keys和key是null,表示没有使用索引。
Eg1:
-- 确保添加索引
-- 将未添加索引的字段添加索引
ALTER TABLE course ADD INDEX cname_index(cname);
-- 分析执行计划
EXPLAIN
SELECT
t.tname,
tc.tcdesc
FROM
teacher t,
teacherCard tc
WHERE t.tcid = tc.tcid
AND t.tid =
(SELECT
c.cid
FROM
course c
WHERE c.cname = 'SQL') ;
Eg2:
-- 确保添加索引
-- 分析执行计划
EXPLAIN
SELECT
tc.tcdesc
FROM
teacherCard tc,
course c,
teacher t
WHERE t.tid = c.tid
AND t.tcid = tc.tcid
AND c.cname = 'SQL' ;
key_len:索引的长度
作用:用于判断复合索引是否被完全使用。
常识:
- utf8:1个字符3个字节
- gbk:1个字符2个字节
- latin:1个字符1个字节
固定长度的索引类型:
Eg1-1:
-- 创建test_kl表用于key_len的试验
CREATE TABLE test_kl (NAME CHAR(20) NOT NULL DEFAULT ''); -- name字段非空
-- 添加单值索引
ALTER TABLE test_kl ADD INDEX name_index(NAME);
-- 分析执行计划
EXPLAIN SELECT * FROM test_kl WHERE NAME = ''; -- key_len = 60
结果
key_len = 60
。原因是在utf8中一个char类型字符占3个字节,所以60 = 3 * 20
。Eg1-2:
-- 添加字段
ALTER TABLE test_kl ADD COLUMN name1 CHAR(20); -- name1字段可以为空
-- 添加单值索引
ALTER TABLE test_kl ADD INDEX name1_index(name1);
-- 分析执行计划
EXPLAIN SELECT * FROM test_kl WHERE name1 = ''; -- key_len = 61
结果
key_len = 61
。如果索引字段可以为null,则会使用1个字节作为标识。Eg1-3:
-- 删除索引
DROP INDEX name_index ON test_kl;
DROP INDEX name1_index ON test_kl;
-- 添加复合索引
ALTER TABLE test_kl ADD INDEX name_name1_index(NAME,name1);
-- 分析执行计划 使用name1字段
EXPLAIN SELECT * FROM test_kl WHERE name1 = ''; -- key_len = 121
-- 分析执行计划 使用name字段
EXPLAIN SELECT * FROM test_kl WHERE name = ''; -- key_len = 60
使用复合索引查询时,使用name字段导致复合索引没有被完全使用,使得
key_len = 60
,使用name1字段使得复合索引被完全使用,key_len = 121
。可变长度的索引类型:
Eg2:
-- 添加新字段
ALTER TABLE test_kl ADD COLUMN name2 VARCHAR(20); -- 可以为null
-- 添加单值索引
ALTER TABLE test_kl ADD INDEX name2_index(name2);
-- 分析执行计划
EXPLAIN SELECT * FROM test_kl WHERE name2 = ''; -- key_len = 63
结果
key_len = 63
。原因是63 = 3 * 20 + 1 (标识null) + 2 (标值可变长度)
。
ref:表之间的引用关系
作用:指明当前表所参照的字段。注意与type中的ref值区分。
Eg:
-- 分析执行计划
EXPLAIN
SELECT
*
FROM
course c,
teacher t
WHERE c.tid = t.tid
AND t.tname = 'tw' ;
上图结果中,where后的条件包含两部分
c.tid = t.tid
以及t.tname = 'tw'
。对于前一部分,c.tid
参照的字段为t
表中的t.tid
,由于c.tid
未设置索引,所以ref的值为null;对于后一部分,t.tname
参照的字段为'tw'
,是一个给定的常量,所以ref的值为const
。给
t
表中的c.tid
添加索引后重新分析执行计划:-- course表的tid字段添加索引
ALTER TABLE course ADD INDEX tid_index(tid);
-- 分析执行计划
EXPLAIN
SELECT
*
FROM
course c,
teacher t
WHERE c.tid = t.tid
AND t.tname = 'tw' ;
添加索引后,
c.tid = t.tid
条件中c
表的c.tid
字段参照了t
表的t.tid
字段,所以ref的值为myDB.t.tid
。
rows:通过索引查询的记录数
Eg:
-- 分析执行计划
EXPLAIN
SELECT
*
FROM
course c,
teacher t
WHERE c.tid = t.tid
AND t.tname = 'tz' ;
-- 查询
SELECT * FROM course c, teacher t WHERE c.tid = t.tid AND t.tname = 'tz';
-- 查询c表
SELECT * FROM course;
-- 查询t表
SELECT * FROM teacher;
上图结果中,
c
表通过索引查询得到的记录数为2条,所以c
表的rows值为2;虽然执行查询语句得到了t
表的2条记录,但是其是重复的,真正通过t
表索引查询得到的记录只有1条,所以t
表的rows值为1。
Extra:额外信息
常见信息:
Using filesort
:性能消耗大;需要“额外”的一次排序(查询),常见于order by语句中。Eg1:单值索引
-- 创建新表
-- 创建新表并添加单值索引
CREATE TABLE test02 (
a1 CHAR(3),
a2 CHAR(3),
a3 CHAR(3),
INDEX idx_a1 (a1),
INDEX idx_a2 (a2),
INDEX idx_a3 (a3)
) ;
-- 分析执行计划
EXPLAIN SELECT * FROM test02 WHERE a1 = '' ORDER BY a1;
EXPLAIN SELECT * FROM test02 WHERE a1 = '' ORDER BY a2;
上图结果中,按字段
a1
排序时Extra的值为Using where
,按字段a2
排序时Extra的值为Using where; Using filesort
。由于查询的字段是a1
,当按a1
排序时,就按照查出来的结果排序即可,然而当按a2
排序时就需要以a2
为字段进行一次额外的查询,然后将查询的结果排序,所以Extra的信息中包含Using filesort
。小结:对于单索引,如果排序和查找是同一字段,则不会出现
Using filesort
的情况,反之则会出现。为了避免这一问题,可以采用如下方法:where哪些字段就order by哪些字段。Eg2:复合索引(满足最左前缀原则)
-- 删除单值索引
DROP INDEX idx_a1 ON test02;
DROP INDEX idx_a2 ON test02;
DROP INDEX idx_a3 ON test02;
-- 添加复合索引
ALTER TABLE test02 ADD INDEX idx_a1_a2_a3(a1,a2,a3);
-- 分析执行计划
EXPLAIN SELECT * FROM test02 WHERE a1 = '' ORDER BY a3; -- Using filesort
EXPLAIN SELECT * FROM test02 WHERE a2 = '' ORDER BY a3; -- Using filesort
EXPLAIN SELECT * FROM test02 WHERE a1 = '' ORDER BY a2;
小结:对于复合索引,为了避免出现
Using filesort
,where和order by按照复合索引的顺序使用,不要跨列或无序使用。
Using temporary
:性能损耗大,用到了临时表,常见于group by语句中。Eg1:
-- 分析执行计划
EXPLAIN SELECT a1 FROM test02 WHERE a1 IN ('1','2','3') GROUP BY a1;
EXPLAIN SELECT a1 FROM test02 WHERE a1 IN ('1','2','3') GROUP BY a2; -- Using temporary
上图结果中,以
a1
索引对字段a1
进行查询却按字段a2
进行分组,导致需要用到临时表,Extra中出现Using temporary
。要避免这种情况可以采用如下方法:查询哪些列就根据那些列group by。
Using index
:性能提升;覆盖索引。原因:出现Using index
,说明不读取原文件,只从索引文件中获取数据,即不需要回表查询。只要是用到的列全部都在索引中,就是覆盖索引。Eg1:test02表中存在复合索引(idx_a1_a2_a3);正例
-- 分析执行计划
EXPLAIN SELECT a1, a2 FROM test02 WHERE a1 = '' OR a2 = '';
上图结果中,由于使用的字段
a1
和字段a2
均包含在复合索引中,是覆盖索引,因此Extra中出现Using index
。Eg2:反例
-- 删除复合索引
DROP INDEX idx_a1_a2_a3 ON test02;
-- 添加字段a1和a2的复合索引
ALTER TABLE test02 ADD INDEX idx_a1_a2(a1, a2);
-- 分析执行计划
EXPLAIN SELECT a1, a3 FROM test02 WHERE a1 = '' OR a3 = '';
上图结果中,使用了字段
a1
和a3
进行查询,而复合索引中不包含字段a3
,因此不是覆盖索引,所以Extra中不会出现Using index
。Eg3: 覆盖索引会对其他属性产生影响
-- 分析执行计划
EXPLAIN SELECT a1, a2 FROM test02 WHERE a1 = '' OR a2 = '';
EXPLAIN SELECT a1, a2 FROM test02;
如果使用覆盖索引(
Using index
),会对possible_keys
和key
造成影响:
- 若没有where,则索引只出现在key中;
- 如果没有索引,则索引出现在possible_keys和key中。
Using where
:可能需要回表查询-- 分析执行计划
EXPLAIN SELECT a1, a3 FROM test02 WHERE a3 = ''; -- 需要回表查询
上图结果中,字段
a3
不在索引中,因此需要回表查询,Extra的信息为Using where
。-- 分析执行计划
EXPLAIN SELECT a1, a2 FROM test02 WHERE a1 = '' OR a2 = '';
上图结果中使用了覆盖索引,所以Extra中包含了
Using index
,但同时Extra的信息中还出现了Using where
,其实此时并未发生回表查询。Using index
和Using where
一起出现时一定不发生回表查询。备注:
Using index condition
与ICP(index condition pushdown)
(MySQL5.6新特性)- 需要回表查询
- https://www.cnblogs.com/thrillerz/p/4166720.html
Impossible WHERE
:where子句永远为false-- 分析执行计划
EXPLAIN SELECT * FROM test02 WHERE a1 = 'x' AND a1 = 'y'; -- where子句永远为false,出现Impossible where
6. 优化案例
单表优化
准备:
-- 创建book表
CREATE TABLE book (
bid INT PRIMARY KEY,
NAME VARCHAR (20) NOT NULL,
authorId INT NOT NULL,
publicId INT NOT NULL,
typeId INT NOT NULL
) ; -- 插入数据
INSERT INTO book VALUES(1, 'tjava', 1, 1, 2);
INSERT INTO book VALUES(2, 'tc', 2, 1, 2);
INSERT INTO book VALUES(3, 'wx', 3, 2, 1);
INSERT INTO book VALUES(4, 'math', 4, 2, 3);
查询:typeId =2或typeId=3且authorID=1的bid
-- 查询
SELECT bid FROM book WHERE typeId IN (2, 3) AND authorId = 1;
-- 分析执行计划
EXPLAIN SELECT bid FROM book WHERE typeId IN (2, 3) AND authorId = 1;
EXPLAIN SELECT bid FROM book WHERE typeId IN (2, 3) AND authorId = 1 ORDER BY typeId DESC;
从结果可以看出,该查询语句性能较低,需要优化。
优化1:添加复合索引
-- 添加索引
ALTER TABLE book ADD INDEX idx_bid_tid_aid(bid, typeId, authorId);
-- 分析执行计划
EXPLAIN SELECT bid FROM book WHERE typeId IN (2, 3) AND authorId = 1 ORDER BY typeId DESC;
从结果看出,type由
all
提升为index
,Extra的信息中出现Using index
,但是Using filesort
仍然存在,继续优化。优化2:按照SQL的实际解析顺序调整索引顺序,重新添加索引。
-- 删除索引
DROP INDEX idx_bid_tid_aid ON book;
-- 按解析顺序添加索引
ALTER TABLE book ADD INDEX idx_tid_aid_bid(typeId, authorId, bid);
-- 分析执行计划
EXPLAIN SELECT bid FROM book WHERE typeId IN (2, 3) AND authorId = 1 ORDER BY typeId DESC;
上图结果中,Extra中的信息中只有
Using index
和Using where
,是覆盖索引,不需要回表查询,效率提升。同时覆盖索引对possible_keys
和key
产生了影响。优化3:提升type级别。因为使用范围查新时in有时会失效,因此交换索引的顺序,同时改变查询语句where子句的顺序。
-- 删除索引
DROP INDEX idx_tid_aid_bid ON book;
-- 按解析顺序添加索引
ALTER TABLE book ADD INDEX idx_aid_tid_bid(authorId, typeId, bid);
-- 分析执行计划
EXPLAIN SELECT bid FROM book WHERE authorId = 1 AND typeId IN (2, 3) ORDER BY typeId DESC;
上图结果中,type由
index
提升至ref
,性能进一步提升。小结:
- 最左前缀原则,保持索引的定义和使用的顺序一致性;
- 索引需要逐步优化;
- 将含in的范围查询放到where子句最后防止失效。
两表优化
准备:
-- 创建表
CREATE TABLE teacher2 (tid INT PRIMARY KEY, cid INT NOT NULL) ;
CREATE TABLE course2 (cid INT, cname VARCHAR (20)) ;
-- 插入数据
INSERT INTO course2 VALUES(1,'java');
INSERT INTO course2 VALUES(2,'python');
INSERT INTO course2 VALUES(3,'kotlin'); INSERT INTO teacher2 VALUES(1,2);
INSERT INTO teacher2 VALUES(2,1);
INSERT INTO teacher2 VALUES(3,3);
Eg:左连接添加索引进行优化
小表驱动大表:
where 小表.x = 大表.x
索引建立在经常使用的字段上
-- 不加索引分析执行计划
EXPLAIN SELECT * FROM teacher2 t LEFT OUTER JOIN course2 c ON t.cid = c.cid WHERE c.cname = 'java';
-- 添加索引
ALTER TABLE teacher2 ADD INDEX index_teacher2_cid(cid);
-- 添加索引分析执行计划
EXPLAIN SELECT * FROM teacher2 t LEFT OUTER JOIN course2 c ON t.cid = c.cid WHERE c.cname = 'java';
上图结果中,添加索引后
t
表的type由all
提升至index
,同时t
表的Extra信息为Using index
。c
表的Extra中出现Using join buffer
表明MySQL引擎使用了连接缓存。Eg:继续添加索引
-- 添加索引
ALTER TABLE course2 ADD INDEX index_course2_cname(cname);
-- 分析执行计划
EXPLAIN SELECT * FROM teacher2 t LEFT OUTER JOIN course2 c ON t.cid = c.cid WHERE c.cname = 'java';
上图结果中,
c
表和t
表的type均提升至ref
。
三表优化
原则:
- 小表驱动大表
- 索引建立在经常查询的字段上
7. 避免索引失效的原则
原则:
复合索引
- 复合索引不要跨列或无序使用(最左前缀原则);
- 复合索引尽量使用全索引匹配。
不要在索引上进行任何操作(计算、函数、类型转换),否则索引失效。
Eg:
-- 查看索引
SHOW INDEX FROM book;
-- 分析执行计划
EXPLAIN SELECT * FROM book WHERE authorId = 1 AND typeId = 2;
EXPLAIN SELECT * FROM book WHERE authorId = 1 AND typeId * 2 = 2;
EXPLAIN SELECT * FROM book WHERE authorId * 2 = 1 AND typeId * 2 = 2;
EXPLAIN SELECT * FROM book WHERE authorId * 2 = 1 AND typeId = 2;
上图结果中,通过
key_len
可以清楚地看出对索引进行操作导致索引失效。值得注意地是,复合索引中,如果左侧失效,其右侧全部失效(最左前缀)。复合索引中不能使用不等于(!= ,<>)或is null(is not null),否则自身以及右侧索引全部失效。由于SQL优化器的原因,大多情况下,范围查询(>, <, in)之后的索引失效。
Eg:
-- 删除添加索引
DROP INDEX idx_aid_tid_bid ON book;
ALTER TABLE book ADD INDEX idx_authorId(authorId);
ALTER TABLE book ADD INDEX idx_typeId(typeId);
-- 分析执行计划
EXPLAIN SELECT * FROM book WHERE authorId = 1 AND typeId = 2;
EXPLAIN SELECT * FROM book WHERE authorId <> 1 AND typeId = 2;
EXPLAIN SELECT * FROM book WHERE authorId <> 1 AND typeId <> 2;
由于MySQL服务层中SQL优化器的存在,SQL优化是一种概率层面的优化。实际中是否使用优化,需要通过explain进行推测。因此在第一次查询结果中,理想情况下应该是使用
idx_authorId
和idx_typeId
两个索引,但实际中只使用了idx_authorId
。第二次查询中由于对idx_authorId
使用了不等于操作,使得idx_authorId
索引失效,而使用了idx_typeId
索引。第三次查询中,两个索引都进行了不等于操作,使得索引都失效。SQL优化器影响的例子:
-- 删除添加索引
DROP INDEX idx_authorId ON book;
DROP INDEX idx_typeId ON book;
ALTER TABLE book ADD INDEX idx_aid_tid(authorId, typeId);
-- 分析执行计划
-- 复合索引全部使用
EXPLAIN SELECT * FROM book WHERE authorId = 1 AND typeId = 2;
-- 复合索引全部失效
EXPLAIN SELECT * FROM book WHERE authorId > 1 AND typeId = 2;
-- 复合索引全部使用
EXPLAIN SELECT * FROM book WHERE authorId = 1 AND typeId > 2;
-- 复合索引部分失效
EXPLAIN SELECT * FROM book WHERE authorId < 1 AND typeId = 2;
-- 复合索引全部失效
EXPLAIN SELECT * FROM book WHERE authorId < 4 AND typeId = 2;
尽量使用覆盖索引(Using index)。
like尽量以
常量
开头,不要以%
开头,否则索引失效。-- 查看索引
SHOW INDEX FROM teacher;
-- 分析执行计划
EXPLAIN SELECT * FROM teacher WHERE tname LIKE 'x%';
EXPLAIN SELECT * FROM teacher WHERE tname LIKE '%x%';
EXPLAIN SELECT tname FROM teacher WHERE tname LIKE '%x%';
上图结果中由于在like后面以
%
开头导致索引失效。如果必须要like后面以%
开头,可以使用覆盖索引(Using index)。尽量不要使用类型转换(显示、隐式),否则索引失效。
-- 分析执行计划
EXPLAIN SELECT * FROM teacher WHERE tname = 'abc';
EXPLAIN SELECT * FROM teacher WHERE tname = 123;
上图结果中,程序底层将
123
转换为'123'
,即进行了类型转换,因此索引失效。尽量不要使用or,否则索引失效。
-- 分析执行计划
EXPLAIN SELECT * FROM teacher WHERE tname = '' AND tcid > 1;
EXPLAIN SELECT * FROM teacher WHERE tname = '' OR tcid > 1;
上图结果中,在使用了or之后,索引失效。
8. 一些其他的优化方法
EXIST和IN
exist和in:如果主查询的数据集大,则使用in,效率高;如果子查询的数据集大,则使用exist,效率高。
exist语法:将主查询的结果放到子查询中进行条件校验(看子查询是否有数据,如果有数据,则校验成功),如果校验成功则保留查询结果,否则不保留。
SELECT tname FROM teacher WHERE EXISTS (SELECT * FROM teacher); -- 有效
SELECT tname FROM teacher WHERE EXISTS (SELECT * FROM teacher WHERE tid = 60); -- 失效
ORDER BY
order by优化:常出现Using filesort,Using filesort有两种排序算法:双路排序、单路排序(根据IO的次数)。
MySQL4.1之前,默认使用双路排序:扫描2次磁盘 - ① 从磁盘读取排序字段并在缓冲区中进行排序;②扫描其他字段。MySQL4.1之后,为了减少IO访问次数消耗性能,默认使用单路排序:只扫描一次磁盘 - 一次读取全部字段并在缓冲区进行排序,但存在隐患(实际上不一定真的是一次IO,可能是多次IO)。原因在于如果数据量特别大则无法将所有数据一次性读取完毕,因此会进行分片多次读取。
注意:
单路排序比双路排序会占用更多的缓冲区(buffer);
单路排序在使用时,如果数据量特别大,可以考虑扩增buffer的容量大小
-- 调整buffer的容量大小 单位byte
SET max_length_for_sort_data = 1024;
如果需要排序的数据(order by 后的字段)总大小超过了
max_length_for_sort_data
定义的字节数,那么MySQL会自动由单路排序切换为双路排序。提高order by查询效率的策略:
- 选择使用单路排序或双路排序,调整buffer容量的大小;
- 尽量避免
select * ...
语句;- 复合索引不要跨列使用,避免出现
Using filesort
;- 尽量保证全部排序字段的排序一致性(都是升序或都是降序)。
9. SQL排查
慢查询日志
MySQL提供的一种日志记录,用于记录MySQL中响应时间超过阀值的SQL语句(
long_query_time
:默认10秒)。慢查询日志默认关闭,在开发调优是建议打开,最终部署时关闭。
检查是否开启了慢查询日志以及开启慢查询日志:
-- 检查是否开启
SHOW VARIABLES LIKE '%slow_query_log%';
-- 临时开启,重启MySQL服务失效
SET GLOBAL slow_query_log = 1;
-- 永久开启
-- 在/etc/my.cnf配置文件中的[mysqld]后追加配置:
-- slow_query_log = 1
-- slow_query_log_file = /vaar/lib/mysql/localhost-slow.log
查询并修改慢查询阀值:
-- 查询慢查询阀值
SHOW VARIABLES LIKE '%long_query_time%';
-- 临时设置慢查询阀值
-- 设置完毕后重新登录生效
SET GLOBAL long_query_time = 3;
-- 永久开启
-- 在/etc/my.cnf配置文件中的[mysqld]后追加配置:
-- long_query_time = 3
Eg:
-- 查询慢查询阀值
SHOW VARIABLES LIKE '%long_query_time%';
-- 查询线程休眠4秒
SELECT SLEEP(4);
-- 查看响应时间超过慢查询阀值的SQL条数
SHOW GLOBAL STATUS LIKE '%slow_queries%';
查看具体的慢SQL:
- 通过慢查询日志可以查看具体的SQL语句:
cat /var/lib/mysql/localhost-slow.log
用
mysqldumpslow
工具查看慢SQL,可以通过一些过滤条件找到需要定位的慢SQL
s
:排序方式;r
:逆序;l
:锁定时间;g
:正则表达式-- 多增加几条慢SQL
SELECT SLEEP(5);
SELECT SLEEP(3);
SELECT SLEEP(3);
SELECT SLEEP(3);
Eg1:获取返回记录最多的3个慢SQL
mysqldumpslow -s r -t 3 /var/lib/mysql/localhost-slow.log
Eg2:获取访问次数最多的3个慢SQL
mysqldumpslow -s c -t 3 /var/lib/mysql/localhost-slow.log
Eg3:按时间排序,前十条包含left join查询语句的SQL
mysqldumpslow -s t -t 10 -g "LEFT JOIN" /var/lib/mysql/localhost-slow.log
10. 分析海量数据
模拟海量数据
利用存储过程(无return)/存储函数(有return):
-- 创建新数据库并切换
CREATE DATABASE testdata;
USE testdata;
-- 创建新表
CREATE TABLE dept (
dno INT PRIMARY KEY DEFAULT 0,
dname VARCHAR (20) NOT NULL DEFAULT '',
loc VARCHAR (20) DEFAULT ''
) ENGINE = INNODB DEFAULT CHARSET = utf8 ; CREATE TABLE emp (
eid INT PRIMARY KEY,
ename VARCHAR (20) NOT NULL DEFAULT '',
job VARCHAR (20) NOT NULL DEFAULT '',
deptno INT NOT NULL DEFAULT 0
) ENGINE = INNODB DEFAULT CHARSET = utf8 ;
-- 通过存储函数插入海量数据
-- 创建随机字符串模拟员工名称
DELIMITER $
CREATE FUNCTION randstring(n INT) RETURNS VARCHAR (255)
BEGIN
DECLARE all_str VARCHAR (100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' ;
DECLARE return_str VARCHAR (255) DEFAULT '' ;
DECLARE i INT DEFAULT 0 ;
WHILE
i < n DO SET return_str = CONCAT(
return_str,
SUBSTRING(all_str, FLOOR(RAND() * 52) + 1, 1)
) ;
SET i = i + 1 ;
END WHILE ;
RETURN return_str ;
END $ -- 创建随机数字模拟编号
DELIMITER $
CREATE FUNCTION ran_num () RETURNS INT (5)
BEGIN
DECLARE i INT DEFAULT 0 ;
SET i = FLOOR(RAND() * 100) ;
RETURN i ; END $ -- 通过存储过程向emp表插入海量数据
DELIMITER $
CREATE PROCEDURE insert_emp (
IN eid_start INT (10),
IN data_times INT (10)
)
BEGIN
DECLARE i INT DEFAULT 0 ;
SET autocommit = 0 ;
REPEAT
INSERT INTO emp
VALUES
(
eid_start + i,
randstring (5),
'other',
ran_num ()
) ;
SET i = i + 1 ;
UNTIL i = data_times
END REPEAT ;
COMMIT ;
END $ -- 通过存储过程向dept表插入海量数据
DELIMITER $
CREATE PROCEDURE insert_dept (
IN dno_start INT (10),
IN data_times INT (10)
)
BEGIN
DECLARE i INT DEFAULT 0 ;
SET autocommit = 0 ;
REPEAT
INSERT INTO dept
VALUES
(
dno_start + i,
randstring (6),
randstring (8)
) ;
SET i = i + 1 ;
UNTIL i = data_times
END REPEAT ;
COMMIT ;
END $ -- 插入数据
CALL insert_emp(1000,800000);
CALL insert_dept(10,30); -- 验证
SELECT COUNT(1) FROM emp;
SELECT COUNT(1) FROM dept;
可能会出现报错:
SQL syntax:SQL语法有错,需修改SQL语句
This function has none of DETERMINISTIC......:慢查询日志冲突,可以按如下方式解决
-- 临时解决
SHOW VARIABLES LIKE '%log_bin_trust_function_creators%';
SET GLOBAL log_bin_trust_function_creators = 1;
-- 永久解决
-- 永久开启
-- 在/etc/my.cnf配置文件中的[mysqld]后追加配置:
-- log_bin_trust_function_creators = 1
分析海量数据
利用
profiles
:当profiling
开启后会记录全部SQL语句的相关信息(id,执行时间和SQL语句)。缺点在于只能看多总执行时间,不能看到各个硬件消耗的时间。-- 查看
SHOW VARIABLES LIKE '%profiling%';
-- 使用
SHOW PROFILES;
-- 开启
SET profiling = ON;
-- 查看
SHOW VARIABLES LIKE '%profiling%';
-- 使用
SHOW PROFILES;
-- 查询
SELECT COUNT(1) FROM dept;
-- 使用
SHOW PROFILES;
精确分析:SQL诊断
-- SQL诊断
SHOW PROFILE ALL FOR QUERY 2;
SHOW PROFILE cpu, block io FOR QUERY 2;
全局查询日志:记录
profileing
开启后的全部SQL语句(全局的记录操作仅仅在调优和开发过程中打开即可,在最终部署时一定关闭),在mysql.general_log表中可以查看日志。-- 查看
SHOW VARIABLES LIKE '%general_log%';
-- 将全部的SQL记录在表中
SET GLOBAL general_log = ON;
SET GLOBAL log_output = 'table';
-- 查看
SHOW VARIABLES LIKE '%general_log%';
-- 查询
SELECT * FROM emp;
SELECT COUNT(*) FROM emp;
-- 查看日志
SELECT * FROM mysql.general_log;
-- 也可以将全部的SQL记录到文件
SET GLOBAL general_log_file = ON;
SET GLOBAL log_output = 'file';
SET GLOBAL general_log_file = '/tmp/general.log';
-- 查询
SELECT COUNT(1) FROM dept;
查看日志文件:
cat /tmp/general.log
11. 锁机制
解决因资源共享而造成的并发问题。
分类:
- 操作类型:
- 读锁(共享锁):对同一数据,多个读操作可以同时进行,互不干扰。
- 写锁(互斥锁):如果当前写操作没有完成,则无法进行其他的读操作和写操作。
- 操作范围:
- 表锁:一次性对整张表加锁。如MyISAM存储引擎使用表锁,开销小,加锁块;无死锁;但锁的范围大,容易发生锁冲突,并发度低。
- 行锁:一次性对一条数据加锁。如InnoDB存储引擎使用行锁,开销大,加锁慢;容易出现死锁;锁的范围较小,不易发生锁冲突,并发度高(发生高并发问题:脏读、修改丢失、不可重复读和幻读)。
- 页锁
表锁(MyISAM)
加读锁
-- 建表设置为MyISAM引擎
CREATE TABLE tablelock (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR (20)
) ENGINE MYISAM ; -- 插入数据
INSERT INTO tablelock VALUES(NULL,'a1');
INSERT INTO tablelock VALUES(NULL,'a2');
INSERT INTO tablelock VALUES(NULL,'a3');
INSERT INTO tablelock VALUES(NULL,'a4');
INSERT INTO tablelock VALUES(NULL,'a5'); -- 查看加锁的表
SHOW OPEN TABLES; -- 加读锁
LOCK TABLE tablelock READ;
会话1(加锁的会话):
如果会话1对表加了read锁,那么会话1可以对该表进行读操作,不能进行写操作;会话1对其他表既不可以进行读操作也不可以进行写操作。换句话说,若会话1对数据库中的一个表加了read锁,那么会话1只能进行对加锁表的读操作。
会话2(其他会话):
其他会话能对加锁表进行读操作,不能进行写操作,可以对其他表进行读操作和写操作。
加锁的会话 其他会话 加锁表的读操作 √ √ 加锁表的写操作 × √ 需要等待锁释放 其他表的读操作 × √ 其他表的写操作 × √ 加写锁:
-- 释放锁
UNLOCK TABLES;
-- 加写锁
LOCK TABLE tablelock WRITE;
加锁的会话 其他会话 加锁表的读操作 √ √ 需要等待锁释放 加锁表的写操作 √ √ 需要等待锁释放 其他表的读操作 × √ 其他表的写操作 × √ MySQL表级锁的锁模式:
MyISAM在执行查询语句(
SELECT
)前会自动给涉及的所有表加read
锁,在执行更新操作(DML
)前会自动给涉及的表加write
锁。所以对MyISAM表进行操作会出现以下情况:
- 对MyISAM表的读操作(加读锁),不会阻塞其他进程(会话)对同一表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,才会执行其他进程的写操作;
- 对MyISAM表的写操作(加写锁),会阻塞其他进程(会话)对同一表的读和写操作,只有当写锁释放后,才会执行其他进程的读写操作。
分析表锁定:
-- 查看加锁的表
SHOW OPEN TABLES;
In_use
:当其值为1时,表示被加了锁。-- 分析表锁定的严重程度
SHOW STATUS LIKE 'table%';
Table_locks_immediate
:可能获取到的锁的数量
Table_lock_waited
:需要等待的表锁数(如果该值越大,说明存在越大的锁竞争)。一般建议:
计算比值
n = Table_locks_immediate / Table_lock_waited
,若n > 5000
,建议采用InnoDB
引擎,否则采用MyISAM
引擎。
行锁(InnoDB)
InnoDB存储引擎默认使用行锁
-- 创建表
CREATE TABLE linelock (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR (20)
) ENGINE = INNODB DEFAULT CHARSET = utf8 ; -- 插入数据
INSERT INTO linelock VALUES(NULL, '1');
INSERT INTO linelock VALUES(NULL, '2');
INSERT INTO linelock VALUES(NULL, '3');
INSERT INTO linelock VALUES(NULL, '4');
INSERT INTO linelock VALUES(NULL, '5');
两个会话进行操作
会话1:
-- 关闭自动提交
SET autocommit = 0;
-- 会话1进行写操作
INSERT INTO linelock VALUES(6,'a6');
会话2:
-- 关闭自动提交
SET autocommit = 0;
-- 会话2对同一条数据进行写操作
UPDATE linelock SET NAME = 'ax' WHERE id = 6;
会话1结果:
会话2结果:
行锁机制:
- 如果会话1对某条数据进行DML操作(关闭自动提交的情况下),则其他操作必须等待会话或事务结束后(commit/rollback)后才能进行操作。
- 表锁通过
UNLOCK TABLES;
释放锁,行锁通过事务解锁(commit/rollback
)。- 行锁一次锁一行数据,因此操作不同行的数据互不干扰。
行锁的注意事项:
- 如果没有索引,则行锁会转为表锁。(注意回顾索引失效的情况)
- 行锁的一种特殊情况(间隙锁):值在范围内,但却不存在。MySQL会自动给间隙加间隙锁。实际中where子句后面加范围查询时,实际加锁的范围就是查询的范围(不是数据库表中实际的值)。
行锁小结:
- InnoDB默认采用行锁;
- 缺点在于相比表锁性能损耗大,优点在于并发能力强以及效率高。
- 建议高并发使用InnoDB存储引擎,否则用MyISAM存储引擎。
分析行锁定:
SHOW STATUS LIKE '%innodb_row_lock%';
Innodb_row_lock_current_waits
:当前正在等待锁的数量
Innodb_row_lock_time
:从系统启动到现在锁定的总时长
Innodb_row_lock_time_avg
:从系统启动到现在锁定的平均时长
Innodb_row_lock_time_max
:从系统启动到现在锁定的最大时长
Innodb_row_lock_waits
:从系统启动到现在等待的次数查询时加锁:
通过
for update
对query语句进行加锁。-- 开启事务
BEGIN
-- 会话1进行查询
SELECT * FROM linelock WHERE id = 2 FOR UPDATE; -- 加锁 -- 会话2进行更新
UPDATE linelock SET NAME = '222' WHERE id = 2; -- 等待锁释放
12. 主从复制
什么是主从复制
主从复制,是用来建立一个和主数据库完全一样的数据库环境,称为从数据库;
主数据库一般是准实时的业务数据库。
主从复制的作用
- 实时灾备,用于故障切换:做数据的热备,作为后备数据库,主数据库服务器故障后,可切换到从数据库继续工作,避免数据丢失。
- 架构扩展,提升机器性能:业务量越来越大,I/O访问频率过高,单机无法满足,此时做多库的存储,降低磁盘I/O访问的频率,提高单个机器的I/O性能。
- 读写分离,避免影响业务:读写分离使数据库能支撑更大的并发。在报表中尤其重要。由于部分报表sql语句非常的慢,导致锁表,影响前台服务。如果前台使用master,报表使用slave,那么报表sql将不会造成前台锁,保证了前台速度。
主从复制的原理
数据库有个bin-log二进制文件,记录了所有sql语句。
目标就是把主数据库的bin-log文件的sql语句复制过来。
使其在从数据库的relay-log重做日志文件中再执行一次这些sql语句即可。
主从复制配置具体需要三个线程:
- binlog输出线程:每当有从库连接到主库的时候,主库都会创建一个线程然后发送binlog内容到从库。在从库里,当复制开始的时候,从库就会创建以下两个线程进行处理。
- 从库I/O线程:当START SLAVE语句在从库开始执行之后,从库创建一个I/O线程,该线程连接到主库并请求主库发送binlog里面的更新记录到从库上。从库I/O线程读取主库的binlog输出线程发送的更新并拷贝这些更新到本地文件,其中包括relay log文件。
- 从库SQL线程:从库创建一个SQL线程,这个线程读取从库I/O线程写到relay log的更新事件并执行。
对于每一个主从复制的连接,都有三个线程。拥有多个从库的主库为每一个连接到主库的从库创建一个binlog输出线程,每一个从库都有它自己的I/O线程和SQL线程。
主从复制的问题及解决方法
存在问题:
- 主库宕机后,数据可能丢失;
- 从库只有一个sql Thread,主库写压力大,复制很可能延时。
解决方法:
- 半同步复制:解决数据丢失的问题
- 并行复制:解决从库复制延迟的问题
MySQL进阶之MySQL索引以及索引优化的更多相关文章
- mysql进阶(四)mysql中select
mysql中select * for update 注: FOR UPDATE 仅适用于InnoDB,且必须在事务区块(BEGIN/COMMIT)中才能生效. 作用 锁定该语句所选择到的对象.防止在 ...
- (3)MySQL进阶篇SQL优化(索引)
1.索引问题 索引是数据库优化中最常用也是最重要的手段之一,通过索引通常可以帮助用户解决大多数 的SQL性能问题.本章节将对MySQL中的索引的分类.存储.使用方法做详细的介绍. 2.索引的存储分类 ...
- mysql进阶(二十六)MySQL 索引类型(初学者必看)
mysql进阶(二十六)MySQL 索引类型(初学者必看) 索引是快速搜索的关键.MySQL 索引的建立对于 MySQL 的高效运行是很重要的.下面介绍几种常见的 MySQL 索引类型. 在数 ...
- MySQL进阶篇(02):索引体系划分,B-Tree结构说明
本文源码:GitHub·点这里 || GitEE·点这里 一.索引简介 1.基本概念 首先要明确索引是什么:索引是一种数据结构,数据结构是计算机存储.组织数据的方式,是指相互之间存在一种或多种特定关系 ...
- MySQL进阶篇(03):合理的使用索引结构和查询
本文源码:GitHub·点这里 || GitEE·点这里 一.高性能索引 1.查询性能问题 在MySQL使用的过程中,所谓的性能问题,在大部分的场景下都是指查询的性能,导致查询缓慢的根本原因是数据量的 ...
- 【夯实Mysql基础】MySQL性能优化的21个最佳实践 和 mysql使用索引
本文地址 分享提纲: 1.为查询缓存优化你的查询 2. EXPLAIN 你的 SELECT 查询 3. 当只要一行数据时使用 LIMIT 1 4. 为搜索字段建索引 5. 在Join表的时候使用相当类 ...
- MySQL索引分析与优化
1.MySQL能够在name的索引中查找“Mike”值,然后直接转到数据文件中相应的行,准确地返回该行的 peopleid(999).在这个过程中,MySQL只需处理一个行就可以返回结果.如果没有“n ...
- 数据库 MySQL进阶之索引
数据库的索引非常重要,基本面试数据库的问题都在索引上,所以这里小编整理出来,一方面为了自己复习,一方面也方便大家. 一,索引前传 在了解数据库索引之前,首先有必要了解一下数据库索引的数据结构基础,那么 ...
- mysql进阶(二十七)数据库索引原理
mysql进阶(二十七)数据库索引原理 前言 本文主要是阐述MySQL索引机制,主要是说明存储引擎Innodb. 第一部分主要从数据结构及算法理论层面讨论MySQL数据库索引的数理基础. ...
随机推荐
- numpy巩固
导包 import numpy as np 创建二维数组 x = np.matrix([[1,2,3],[4,5,6]]) 创建一维数组 y = np.matrix([1,2,3,4,5,6]) x ...
- FPAG_Microblaze_PWM_定时器
由于Xilinx底层库的定时器没有PWM例程,调试过程中费了不少劲.生产PWM需要两个定时器同时工作,一个控制频率,一个控制占空比,位数可通过硬件设置. #include "xtmrctr_ ...
- C/C++编程笔记:C语言写推箱子小游戏,大一学习C语言练手项目
C语言,作为大多数人的第一门编程语言,重要性不言而喻,很多编程习惯,逻辑方式在此时就已经形成了.这个是我在大一学习 C语言 后写的推箱子小游戏,自己的逻辑能力得到了提升,在这里同大家分享这个推箱子小游 ...
- 2020牛客暑期多校训练营 第二场 A All with Pairs 字符串hash KMP
LINK:All with Pairs 那天下午打这个东西的时候状态极差 推这个东西都推了1个多小时 (比赛是中午考试的我很困 没睡觉直接开肝果然不爽 一开始看错匹配的位置了 以为是\(1-l\)和\ ...
- 当asp.net core偶遇docker一(模型验证和Rabbitmq 一)
比如我们有一些设计,依赖于某些软件,比如rabbitmq 当管理员功能,反复错误三五次之后,就发送一条消息到队列里去,我们又不希望对原先设计带来侵入式的改变业务 这个时候,我们就可以在模型验证里面加入 ...
- intellij idea springboot无法读取配置文件的解决方法
问题描述 Spring Boot项目中运行之后,出现如下的错误: *************************** APPLICATION FAILED TO START *********** ...
- “随手记”APP与已经发布的记账软件“鲨鱼记账”的差距
我们使用并观察了“鲨鱼记账”APP,发现,我们的软件真的还有很多不足的地方.就功能这方面来说:“鲨鱼记账”APP有更多的收入.支出分类:就界面来说:“鲨鱼记账”APP有比我们优美太多的页面和背景.但是 ...
- Python使用socketServer包搭建简易服务器过程详解
官方提供了socketserver包去方便我们快速的搭建一个服务器框架. 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经做案例的 ...
- Ant Design Pro V5 从服务器请求菜单(typescript版)
[前言] 找了很多Admin模板,最后还是看中了AntDesignPro(下文简写antd pro)这个阿里巴巴开源的Admin框架,长这样(还行吧,目前挺主流的): 官网地址:https://pro ...
- Java—匿名对象/内部类/访问修饰符/代码块
匿名对象 匿名对象是指创建对象时,只有创建对象的语句,却没有把对象地址值赋值给某个变量. //创建一个普通对象 Person p = new Person(); //创建一个匿名对象 new Pers ...