Mysql学习之路(五)SQL语句优化、慢查询、执行计划

  本系列文章:
    Mysql学习之路(一)数据类型、关联查询、常用函数、事务、视图
    Mysql学习之路(二)Mysql SQL语句练习题
    Mysql学习之路(三)索引、存储引擎、锁
    Mysql学习之路(四)Mysql架构、Mysql数据库优化
    Mysql学习之路(五)SQL语句优化、慢查询、执行计划

前言 SQL优化的一般步骤

步骤1、通过 show status 命令了解各种 SQL 的执行频率

  通过show [session|global]status命令可以提供服务器状态信息。 可以加上参数“session”或者“global”来显示 session 级(当前连接)的统计结果和 global 级(自数据库上次启动至今)的统计结果。如果不写,默认使用参数是“session”。
  show status like 'Com_%'命令可以获取当前 session 中所有统计参数的值,结果示例:

  Com_xxx 表示每个 xxx 语句执行的次数:

  1. Com_select:执行 select 操作的次数,一次查询只累加 1。
  2. Com_insert:执行 INSERT 操作的次数,对于批量插入的 INSERT 操作,只累加一次。
  3. Com_update:执行 UPDATE 操作的次数。
  4. Com_delete:执行 DELETE 操作的次数。

  上面这些参数对于所有存储引擎的表操作都会进行累计。下面这几个参数只是针对InnoDB 存储引擎的:

  1. Innodb_rows_read:select 查询返回的行数。
  2. Innodb_rows_inserted:执行 INSERT 操作插入的行数。
  3. Innodb_rows_updated:执行 UPDATE 操作更新的行数。
  4. Innodb_rows_deleted:执行 DELETE 操作删除的行数。

步骤2、定位执行效率较低的 SQL 语句

  • 1、通过慢查询日志定位那些执行效率较低的 SQL 语句。
  • 2、慢查询日志在查询结束以后才纪录,所以在应用反映执行效率出现问题的时候查询慢查询日志并不能定位问题,可以使用show processlist命令查看当前MySQL在进行的线程,包括线程的状态、是否锁表等,可以实时地查看 SQL 的执行情况

步骤3、通过 EXPLAIN 分析低效 SQL 的执行计划

步骤4、通过show profile分析SQL

步骤5、确定问题并采取相应的优化措施

一、慢查询

  MySQL的慢查询日志是MySQL提供的一种日志记录,用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time(默认值为10,意思是运行10S以上的语句)值的SQL,则会被记录到慢查询日志中。

1.1 慢查询日志参数设定

  开启慢查询日志是有代价的,所以它默认是关闭的。查看的命令:

	show variables like 'slow_query%';

  临时打开慢日志查询的命令是:

	set global slow_query_log = on; 

  关闭慢日志查询的命令是:

	set global slow_query_log = off;

  有一个参数,控制执行超过多长时间的 SQL 才记录到慢日志,默认是 10 秒。比如要设置为1秒:

	set long_query_time = 1;

  常见的慢日志查询参数:

slow_query_log:是否开启慢查询日志
long_query_time:查询阈值,超过了该阈值则记录到慢查询日志中;
log_output:如何存储慢查询日志,可选项:慢查询日志可以存储到文件(file),也可以存储到表(table)中;
slow_query_log_file以FILE类型存储慢查询日志时的存储位置`。

  默认情况下,如果没有为慢查询日志指定名称,默认为host_name-slow.log,示例:

  或者修改配置文件my.cnf,以下配置定义了慢查询日志的开关、慢查询的时间、日志文件的存放路径:

slow_query_log = ON
long_query_time = 2
slow_query_log_file = /var/lib/mysql/localhost-slow.log

1.2 慢查询日志分析

  一些命令:

	-- 查看有多少慢查询
	show global status like 'slow_queries';
	-- 获取慢日志目录 
	show variables like '%slow_query%'; 

  日志名称:/var/lib/mysql/ localhost-slow.log。
  日志内容示例:

1.2.1 mysqldumpslow

  mysqldumpslow是MYSQL自带的工具,即可以直接使用该命令。mysqldumpslow一些常见的参数:

-s,以什么方式排序
c 计数
l 锁定时间
r 返回记录
t 查询时间
-t,是top n的意思,即为返回前面多少条的数据
-g,后边可以写一个正则匹配模式,大小写不敏感的

  比如看一下用时最多的 20 条慢 SQL:

	mysqldumpslow -s t -t 20 -g 'select' /var/lib/mysql/localhost-slow.log

  结果示例:

  Count 代表这个 SQL 执行了多少次;
  Time 代表执行的时间,括号里面是累计时间;
  Lock 表示锁定的时间,括号是累计;
  Rows 表示返回的记录数,括号是累计。

1.2.2 show profile

  SHOW PROFILE可以分析当前会话中sql语句执行的资源(比如 CPU、IO )消耗情况的工具,可用于sql调优的测量。默认情况下处于关闭状态,并保存最近15次的运行结果。
  SHOW PROFILE默认是关闭的,使用前需开启。

  show profiles示例:

  使用show profile对sql语句进行诊断:

	--Query_ID为#3步骤中show profiles列表中的Query_ID
	show profile cpu,block io for query Query_ID;

  比如执行:show profile cpu,block io for query 15:

  show profile的常用查询参数:

ALL:显示所有的开销信息。
BLOCK IO:显示块IO开销。
CONTEXT SWITCHES:上下文切换开销。
CPU:显示CPU开销信息。
IPC:显示发送和接收开销信息。
MEMORY:显示内存开销信息。
PAGE FAULTS:显示页面错误开销信息。
SOURCE:显示和Source_function,Source_file,Source_line相关的开销信息。
SWAPS:显示交换次数开销信息。

1.2.3 其他系统命令

  • 1、show processlist
      show processlist:显示用户运行线程。一般用到 show processlist 或 show full processlist 都是为了查看当前 mysql 是否有压力,都在跑什么语句,当前语句耗时多久了,有没有什么慢 SQL 正在执行之类的。可以看到总共有多少链接数,哪些线程有问题(time是执行秒数,时间长的就应该多注意了),然后可以把有问题的线程 kill 掉,这样可以临时解决一些突发性的问题。
      示例:

mysql> show processlist;
+—–+————-+——————–+
| Id | User | Host | db | Command | Time| State | Info
+—–+————-+——————–+
|207|root |192.168.0.2:51621 |mytest | Sleep | 5 | | NULL
|208|root |192.168.0.2:51622 |mytest | Sleep | 5 | | NULL
|220|root |192.168.0.2:51676 |mytest |Query | 84 | locked |

  列的含义和用途:

  1. id列:一个标识,你要kill 一个语句的时候很有用。
  2. user列: 显示当前用户,如果不是root,这个命令就只显示你权限范围内的sql语句。
  3. host列:显示这个语句是从哪个ip 的哪个端口上发出的。可用来追踪出问题语句的用户。
  4. db列:显示这个进程目前连接的是哪个数据库
  5. command列:显示当前连接的执行的命令,一般就是休眠(sleep),查询(query),连接(connect)。
  6. time列:此这个状态持续的时间,单位是秒。
  7. state列:显示使用当前连接的sql语句的状态,很重要的列,后续会有所有的状态的描述,请注意,state只是语句执行中的某一个状态,一个sql语句,以查询为例,可能需要经过copying to tmp table,Sorting result,Sending data等状态才可以完成。
  8. info列:显示这个sql语句,因为长度有限,所以长的sql语句就显示不全,但是一个判断问题语句的重要依据。

  processlist可以用在一些SQL中,如:按客户端 IP 分组,看哪个客户端的链接数最多:

select client_ip,count(client_ip) as client_num 
	from (select substring_index(host,':' ,1) as client_ip 
	from information_schema.processlist ) as connect_info 
	group by client_ip order by client_num desc;

  结果示例:

  查看正在执行的线程,并按 Time 倒排序,看看有没有执行时间特别长的线程:

	select * from information_schema.processlist 
		where Command != 'Sleep' order by Time desc;

  找出所有执行时间超过 5 分钟的线程,拼凑出 kill 语句,方便后面查杀:

	select concat('kill ', id, ';') from information_schema.processlist 
		where Command != 'Sleep' and Time > 300 order by Time desc;

  结果示例:

  查询线程及相关信息,使用命令:

	show full processlist

  结果示例:

  ID 为此线程ID,Time为线程运行时间,Info为此线程SQL。

  • 2、show status
      查看 MySQL 服务器运行状态。有 session和 global 两种作用域,格式:参数-值。可以用 like 带通配符过滤,示例:
	-- 查看 select 次数
	SHOW GLOBAL STATUS LIKE 'com_select'; 
  • 3、show engine
      显示存储引擎的当前运行信息,包括事务持有的表锁、行锁信息;事务的锁等待情况;线程信号量等待;文件 IO 请求;buffer pool 统计信息。示例:
	show engine innodb status;

  如果需要将监控信息输出到错误信息 error log 中(15 秒钟一次),可以开启输出:

	-- 开启输出
	show variables like 'innodb_status_output%';
	SET GLOBAL innodb_status_output=ON;
	SET GLOBAL innodb_status_output_locks=ON;

1.3 慢查询怎么优化

  慢查询的优化首先要搞明白慢的原因是什么? 是查询条件没有命中索引?是load了不需要的数据列还是数据量太大
  所以优化也是针对这三个方向来的:

  • 1、首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写
  • 2、分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引
  • 3、如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表

二、执行计划

2.1 执行计划使用简介

  对于低性能的SQL语句的定位,最重要也是最有效的方法就是使用执行计划,MySQL提供了explain命令来查看语句的执行计划

  不管是哪种数据库,或者是哪种数据库引擎,在对一条SQL语句进行执行的过程中都会做很多相关的优化,对于查询语句,最重要的优化方式就是使用索引。

  执行计划:就是显示数据库引擎对于SQL语句的执行的详细情况,其中包含了是否使用索引,使用什么索引,使用的索引的相关信息等。执行计划示例:

  • 1、id
      执行计划包含的信息id由一组数字组成,id表示一个查询中各个子查询的执行顺序;
  1. id相同执行顺序由上至下。
  2. id不同,id值越大优先级越高,越先被执行
  3. id为null时表示一个结果集,不需要使用它查询,常出现在包含union等查询语句中。
  • 2、select_type
      每个子查询的查询类型,一些常见的查询类型:
id select_type description
1 SIMPLE 不包含任何子查询或union等查询
2 PRIMARY 包含子查询最外层查询就显示为 PRIMARY
3 SUBQUERY 在select或 where字句中包含的查询
4 DERIVED from字句中包含的查询
5 UNION 出现在union后的查询语句中
6 UNION RESULT 从UNION中获取结果集,例如上文的第三个例子
  • 3、table
      查询的数据表,当从衍生表中查数据时会显示 x 表示对应的执行计划id partitions 表分区、表创建的时候可以指定通过那个列进行表分区。
  • 4、type(非常重要,可以看到有没有走索引)
      访问类型:

ALL:扫描全表数据
index:遍历索引
range:索引范围查找
index_subquery:在子查询中使用 ref
unique_subquery:在子查询中使用 eq_ref
ref_or_null:对Null进行索引的优化的 ref
fulltext:使用全文索引
ref:使用非唯一索引查找数据
eq_ref:在join查询中使用PRIMARY KEYorUNIQUE NOT NULL索引关联。

  • 5、possible_keys
      可能使用的索引,注意不一定会使用。查询涉及到的字段上若存在索引,则该索引将被列出来。当该列为 NULL时就要考虑当前的SQL是否需要优化了。
  • 6、key
      在查询中实际使用的索引,若没有使用索引,显示为NULL。
  • 7、key_len
      索引长度。
  • 8、ref
      表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值。
  • 9、rows
      返回估算的结果集数目,并不是一个准确的值。
  • 10、extra
  • 执行计划给出的额外的信息说明,常见的有:
  • Using index 使用覆盖索引;
  • Using where 使用了用where子句来过滤结果集;
  • Using filesort 使用文件排序,使用非索引列进行排序时出现,非常消耗性能,尽量优化;
  • Using temporary 使用了临时表。
      假如有三张表:
	--课程表
	DROP TABLE IF EXISTS course;
	CREATE TABLE `course` (
		`cid` int(3) DEFAULT NULL,
		`cname` varchar(20) DEFAULT NULL,
		`tid` int(3) DEFAULT NULL
	) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
	
	--教师表
	DROP TABLE IF EXISTS teacher;
	CREATE TABLE `teacher` (
		`tid` int(3) DEFAULT NULL,
		`tname` varchar(20) DEFAULT NULL,
		`tcid` int(3) DEFAULT NULL
	) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
	
	--教师联系方式表
	DROP TABLE IF EXISTS teacher_contact;
	CREATE TABLE `teacher_contact` (
		`tcid` int(3) DEFAULT NULL,
		`phone` varchar(200) DEFAULT NULL
	) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

	--往课程表插入数据
	INSERT INTO `course` VALUES ('1', 'mysql', '1');
	INSERT INTO `course` VALUES ('2', 'jvm', '1');
	INSERT INTO `course` VALUES ('3', 'juc', '2');
	INSERT INTO `course` VALUES ('4', 'spring', '3');

	--往教师表插入数据
	INSERT INTO `teacher` VALUES ('1', 'qingshan', '1');
	INSERT INTO `teacher` VALUES ('2', 'jack', '2');
	INSERT INTO `teacher` VALUES ('3', 'mic', '3');

	--往教师联系方式表插入数据
	INSERT INTO `teacher_contact` VALUES ('1', '13688888888');
	INSERT INTO `teacher_contact` VALUES ('2', '18166669999');
	INSERT INTO `teacher_contact` VALUES ('3', '17722225555');

2.2 ID情况

  • 1、id 值不同
      id 值不同的时候,先查询 id 值大的(先大后小)。
      比如用嵌套查询的方式查询Mysql课程的老师手机号,示例:
EXPLAIN SELECT tc.phone
	FROM teacher_contact tc
	WHERE tcid = (
		SELECT tcid
		FROM teacher t
		WHERE t.tid = (
			SELECT c.tid
			FROM course c
			WHERE c.cname = 'mysql'
		)
	);

  查询顺序:course c —> teacher t —> teacher_contact tc。

  先查课程表,再查老师表,最后查老师联系方式表。子查询只有拿到内层的结果之后才能进行外层的查询。

  • 2、id 值相同
      比如查询课程 ID 为 2, 或者联系表 ID 为 3 的老师,示例:
EXPLAIN
SELECT t.tname,c.cname,tc.phone
FROM teacher t, course c, teacher_contact tc
WHERE t.tid = c.tid
AND t.tcid = tc.tcid
AND (c.cid = 2
OR tc.tcid = 3);


  id 值相同时,表的查询顺序是从上往下顺序执行。例如这次查询的 id 都是 1,查询的顺序是 teacher t(3 条) —> course c(4 条)—> teacher_contact tc(3 条)。
  teacher 表插入 3 条数据后:

	INSERT INTO `teacher` VALUES (4, 'james', 4);
	INSERT INTO `teacher` VALUES (5, 'tom', 5);
	INSERT INTO `teacher` VALUES (6, 'seven', 6);
	COMMIT;-- ( 备份) 恢复语句
	DELETE FROM teacher where tid in (4,5,6);
	COMMIT;

  id 也都是 1,但是从上往下查询顺序变成了:teacher_contact tc(3 条) —> teacher t(6 条) —> course c(4 条)。

  可以看到:不同表数据量不同的时候顺序查询顺序会发生变化,这个是由笛卡尔积决定的。
  举例:假如有 a、b、c 三张表,分别有 2、3、4 条数据,如果做三张表的联合查询,当查询顺序是 a→b→c 的时候,它的笛卡尔积是:2*3*4=6*4=24。如果查询顺序是 c→b→a,它的笛卡尔积是 4*3*2=12*2=24。因为 MySQL 要把查询的结果,包括中间结果和最终结果都保存到内存,所以 MySQL会优先选择中间结果数据量比较小的顺序进行查询。所以最终联表查询的顺序是 a→b→c。这个就是为什么 teacher 表插入数据以后查询顺序会发生变化。

  • 3、id 值既有相同也有不同
      如果 ID 有相同也有不同,就是 ID 不同的先大后小,ID 相同的从上往下。

2.3 select_type

  列举一些常见的查询类型。

  • 1、SIMPLE
      简单查询,不包含子查询,不包含关联查询 union。示例:
EXPLAIN SELECT * FROM teacher;

  • 2、PRIMARY和SUBQUERY
      包含子查询的示例:
-- 查询Mysql课程的老师手机号
EXPLAIN SELECT tc.phone
	FROM teacher_contact tc
	WHERE tcid = (
		SELECT tcid
		FROM teacher t
		WHERE t.tid = (
			SELECT c.tid
			FROM course c
			WHERE c.cname = 'mysql'
		)
	);


  PRIMARY子查询 SQL 语句中的主查询,也就是最外面的那层查询。
  SUBQUERY子查询中所有的内层查询都是 SUBQUERY 类型的

  • 3、DERIVED、UNION和UNION RESULT
      查询 ID 为 1 或 2 的老师教授的课程,示例:
EXPLAIN SELECT cr.cname
	FROM (
		SELECT * FROM course WHERE tid = 1
		UNION
		SELECT * FROM course WHERE tid = 2
	) cr;


  DERIVED:衍生查询,表示在得到最终查询结果之前会用到临时表。
  UNION:用到了 UNION 查询。
  UNION RESULT:主要是显示哪些表之间存在 UNION 查询。<union2,3>代表 id=2 和 id=3 的查询存在 UNION。

2.4 type

  在常用的连接类型中:system > const > eq_ref > ref > range > index > all。
  除了 all,都能用到索引。

  • 1、const
      主键索引或者唯一索引,只能查到一条数据的 SQL。
DROP TABLE IF EXISTS single_data;
CREATE TABLE single_data(
id int(3) PRIMARY KEY,
content varchar(20));
insert into single_data values(1,'a');
EXPLAIN SELECT * FROM single_data a where id = 1;

  • 2、system
      system 是 const 的一种特例,只有一行满足条件,示例:
EXPLAIN SELECT * FROM mysql.proxies_priv;

  • 3、eq_ref
      通常出现在多表的 join 查询,表示对于前表的每一个结果,,都只能匹配到后表的一行结果。一般是唯一性索引的查询(UNIQUE 或 PRIMARY KEY)。eq_ref 是除 const 之外最好的访问类型。
select t.tcid from teacher t,teacher_contact tc where t.tcid = tc.tcid;

system,const,eq_ref,都是可遇而不可求的,基本上很难优化到这个状态。

  • 4、ref
      查询用到了非唯一性索引,或者关联操作只使用了索引的最左前缀,如使用 tcid 上的普通索引查询:
explain SELECT * FROM teacher where tcid = 3;

  • 5、range
      索引范围扫描。如果 where 后面是 between and 或 <或 > 或 >= 或 <=或 in 这些,type 类型就为 range。
      不走索引一定是全表扫描(ALL),所以先对要查询的字段加上普通索引。此时执行范围查询(字段上有普通索引):
EXPLAIN SELECT * FROM teacher t WHERE t.tid <3;-- 或
EXPLAIN SELECT * FROM teacher t WHERE tid BETWEEN 1 AND 2


  IN 查询也是 range(字段有主键索引):

EXPLAIN SELECT * FROM teacher_contact t WHERE tcid in (1,2,3);

  • 6、index
      查询全部索引中的数据,示例:
EXPLAIN SELECT tid FROM teacher;

  • 7、all
      如果没有索引或者没有用到索引,type 就是 ALL。代表全表扫描。
  • 8、NULL
      不用访问表或者索引就能得到结果,如:
EXPLAIN select 1 from dual where 1=1;

一般来说,需要保证查询至少达到 range 级别,最好能达到 ref。ALL(全表扫描)和 index(查询全部索引)都是需要优化的。

2.5 possible_key、 key

  可能用到的索引和实际用到的索引。如果是 NULL 就代表没有用到索引。possible_key 可以有一个或者多个。
  此处有一个特殊的情况是possible_key为null,但是实际上用到了索引。
  比如表上创建联合索引:

ALTER TABLE user_innodb DROP INDEX comidx_name_phone;
ALTER TABLE user_innodb add INDEX comidx_name_phone (name,phone);

  执行计划(改成 select name 也能用到索引):

explain select phone from user_innodb where phone='126';


  如果通过分析发现没有用到索引,就要检查 SQL 或者创建索引

2.6 key_len

  索引的长度,单位为字节。跟索引字段的类型、长度有关。

2.7 rows

  MySQL 认为扫描多少行才能返回请求的数据,是一个预估值。一般来说行数越少越好。

2.8 filtered

  这个字段表示存储引擎返回的数据在 server 层过滤后,剩下多少满足查询的记录数量的比例,它是一个百分比。

2.9 ref

  使用哪个列或者常数和索引一起从表中筛选数据。

2.10 Extra

  执行计划给出的额外的信息说明

  • 1、using index
      用到了覆盖索引,不需要回表,示例:
EXPLAIN SELECT tid FROM teacher ;

  覆盖索引的简单理解:select的数据列只用从索引中就能够取得,不必从数据表中读取,换句话说查询列要被所使用的索引覆盖

  • 2、using where
      使用了 where 过滤,表示存储引擎返回的记录并不是所有的都满足查询条件,需要在 server 层进行过滤(跟是否使用索引没有关系)。示例:
EXPLAIN select * from user_innodb where phone ='13866667777';

  • 3、Using index condition( 索引条件下推)
  • 4、using filesort
      不能使用索引来排序,用到了额外的排序,例如:order by,示例:
EXPLAIN select * from user_innodb where name ='青山' order by id;

  • 5、using temporary
      用到了临时表。例如:
EXPLAIN select DISTINCT(tid) from teacher t; --distinct 非索引列
EXPLAIN select tname from teacher group by tname; --group by 非索引列
EXPLAIN select t.tid from teacher t join course c on t.tid = c.tid group by t.tid;   --使用 join 的时候,group 任意列

  总结一下:模拟优化器执行 SQL 查询语句的过程,来知道 MySQL 是怎么处理一条 SQL 语句的。通过这种方式我们可以分析语句或者表的性能瓶颈分析出问题之后,就是对 SQL 语句的具体优化,比如怎么用到索引,怎么减少锁的阻塞等待

三、SQL优化

3.1 SQL的生命周期

  • 1、应用服务器与数据库服务器建立一个连接;
  • 2、数据库进程拿到请求sql;
  • 3、解析并生成执行计划,执行;
  • 4、读取数据到内存并进行逻辑处理;
  • 5、通过步骤一的连接,发送结果到客户端;
  • 6、关掉连接,释放资源。

3.2 优化INSERT语句

  如果同时从同一客户插入很多行,尽量使用多个值表的 INSERT 语句,这种方式将大大缩减客户端与数据库之间的连接、关闭等消耗,使得效率比分开执行的单个 INSERT 语句快。一次插入多值示例:

	insert into test values(1,2),(1,3),(1,4)...

3.3 优化ORDER BY语句

  • 1、不要用ORDER BY RAND()
      MySQL 会不得不去执行 RAND()函数(很耗 CPU 时间),而且这是为了每一行记录去记行,然后再对其排序。
  • 2、从排序角度优化ORDER BY
      Mysql中有两种排序方式:第一种方式是通过有序索引顺序扫描直接返回有序数据,这种方式不需要额外的排序,操作效率较高。第二种方式是通过对返回数据进行排序,也就是Filesort排序,所有不是通过索引直接返回排序结果的排序都是Filesort排序。
      从排序角度看优化目标就是:尽量减少额外的排序,通过索引直接返回有序数据。
      如果WHERE 条件和 ORDER BY 使用相同的索引,并且 ORDER BY 的顺序和索引顺序相同,并且ORDER BY 的字段都是升序或者都是降序,则不需要额外的排序,否则就会进行Filesort排序。
      下列包含ORDER BY的SQL可以使用索引:
	SELECT * FROM t1 ORDER BY key_part1,key_part2,... ;
	SELECT * FROM t1 WHERE key_part1=1 ORDER BY key_part1 DESC, key_part2 DESC;
	SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 DESC;

  下列包含ORDER BY的SQL则不使用索引:

	--order by 的字段混合 ASC 和 DESC
	SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 ASC;
	--用于查询行的关键字与 ORDER BY 中所使用的不相同
	SELECT * FROM t1 WHERE key2=constant ORDER BY key1;
	--对不同的关键字使用 ORDER BY
	SELECT * FROM t1 ORDER BY key1, key2;

3.4 优化JOIN查询

  • 确定ON或者USING子句中是否有索引。
  • 确保GROUP BY和ORDER BY只有一个表中的列,这样Mysql才有可能使用索引。

3.5 优化子查询

  • 用关联查询替代子查询(因为在进行子查询时,Mysql会在内存中创建临时表);
  • 优化GROUP BY和DISTINCT;
  • 使用索引来优化,是最有效的优化方法
  • 关联查询中,使用标识列分组的效率更高;
  • 如果不需要ORDER BY,进行GROUP BY时加ORDER BY NULL,MySQL不会再进行文件排序。

3.6 优化OR语句

  对于含有 OR 的查询子句,如果要利用索引,则 OR 之间的每个条件列都必须用到索引;如果没有索引,则应该考虑增加索引。

3.7 优化分页查询

  优化分页示例:

	-- 大偏移量的 limit
	select * from user_innodb limit 900000,10;
	-- 改成先过滤 ID,再 limit
	SELECT * FROM user_innodb WHERE id >= 900000 LIMIT 10;

  类似于select * from table where age > 20 limit 1000000,10这种查询其实也是有可以优化的余地的。这条语句需要load1000000数据然后基本上全部丢弃,只取10条当然比较慢.。当时我们可以修改为

	select * from table 
		where id in (select id from table where age > 20 limit 1000000,10)

  这样虽然也load了一百万的数据,但是由于索引覆盖,要查询的所有字段都在索引中,所以速度会很快。同时如果ID连续的话,我们还可以select * from table where id > 1000000 limit 10,效率也是不错的,优化的可能性有许多种,但是核心思想都一样,就是减少load的数据

3.8 大表数据查询,怎么优化

  • 1、优化Schema(数据库的组织和结构)、sql语句+索引
  • 2、加缓存,memcached, redis;
  • 3、主从复制,读写分离
  • 4、垂直拆分,根据你模块的耦合度,将一个大的系统分为多个小的系统,也就是分布式系统;
  • 5、水平切分,针对数据量大的表,这一步最麻烦,最能考验技术水平,要选择一个合理的sharding key(切分键), 为了有好的查询效率,表结构也要改动,做一定的冗余,应用也要改,sql中尽量带sharding key,将数据定位到限定的表上去查,而不是扫描全部的表。

3.9 为什么要尽量设定一个主键

  主键是数据库确保数据行在整张表唯一性的保障,即使业务上本张表没有主键,也建议添加一个自增长的ID列作为主键。设定了主键之后,在后续的删改查的时候可能更加快速以及确保操作数据范围安全。

3.10 主键使用自增ID还是UUID

  推荐使用自增ID,不要使用UUID。
  因为在InnoDB存储引擎中,主键索引是作为聚簇索引存在的,也就是说,主键索引的B+树叶子节点上存储了主键索引以及全部的数据(按照顺序),如果主键索引是自增ID,那么只需要不断向后排列即可,如果是UUID,由于到来的ID与原来的大小不确定,会造成非常多的数据插入,数据移动,然后导致产生很多的内存碎片,进而造成插入性能的下降。
  总之,在数据量大一些的情况下,用自增主键性能会好一些
  关于主键是聚簇索引,如果没有主键,InnoDB会选择一个唯一键来作为聚簇索引,如果没有唯一键,会生成一个隐式的主键。

3.11 字段为什么要求定义为not null

  null值会占用更多的字节,且会在程序中造成很多与预期不符的情况。

3.12 如果要存储用户的密码散列,应该使用什么字段进行存储

  密码散列,盐,用户身份证号等固定长度的字符串应该使用char而不是varchar来存储,这样可以节省空间且提高检索效率。

3.13 优化查询过程中的数据访问

  避免犯如下SQL语句错误:

  • 1、查询不需要的数据
      解决办法:使用limit
  • 2、多表关联返回全部列
      解决办法:指定列名
  • 3、总是返回全部列
      解决办法:避免使用SELECT *
  • 4、重复查询相同的数据
      解决办法:可以缓存数据,下次直接读取缓存。
  • 5、是否在扫描额外的记录
      使用explain进行分析,如果发现查询需要扫描大量的数据,但只返回少数的行,可以通过如下技巧去优化:
  1. 使用索引覆盖扫描,把所有的列都放到索引中,这样存储引擎不需要回表获取对应行就可以返回结果。
  2. 改变数据库和表的结构,修改数据表范式。
  3. 重写SQL语句,让优化器可以以更优的方式执行查询。

3.14 优化特定类型的查询语句

  • count( * )会忽略所有的列,直接统计所有列数,不要使用count(列名);
  • MyISAM中,没有任何where条件的count(*)非常快;
  • 当有where条件时,MyISAM的count统计不一定比其它引擎快;
  • 可以使用explain查询近似值,用近似值替代count(*);
  • 增加汇总表;
  • 使用缓存 。

3.15 优化LIMIT分页

  • LIMIT偏移量大的时候,查询效率较低;
  • 可以记录上次查询的最大ID,下次查询时直接根据该ID来查询;
  • 当只要一行数据时使用 LIMIT 1。

3.16 优化WHERE子句

  • 1、对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引
  • 2、尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,示例:
	select id from t where num is null
	-- 可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
	select id from t where num=0
  • 3、尽量避免在 where 子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行全表扫描
  • 4、尽量避免在 where 子句中使用or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,示例:
	select id from t where num=10 or num=20
	-- 可以这样查询:
	select id from t where num=10 union all select id from t where num=20
  • 5、in 和 not in 也要慎用,否则会导致全表扫描,示例:
	select id from t where num in(1,2,3) 
	-- 对于连续的数值,能用 between 就不要用 in 了
	select id from t where num between 1 and 3
  • 6、下面的查询也将导致全表扫描:select id from t where name like ‘%李%’若要提高效率,可以考虑全文检索。
  • 7、如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然 而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:
	select id from t where num=@num
	-- 可以改为强制查询使用索引:
	select id from t with(index(索引名)) where num=@num
  • 8、尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描,示例:
	select id from t where num/2=100
	-- 应改为:
	select id from t where num=100*2
  • 9、尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
	select id from t where substring(name,1,3)='abc'
	-- name以abc开头的id应改为:
	select id from t where name like 'abc%'
  • 10、不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。

3.17 永远为每张表设置一个ID

  我们应该为数据库里的每张表都设置一个 ID 做为其主键,而且最好的是一
个 INT 型的(推荐使用 UNSIGNED),并设置上自动增加的 AUTO_INCREMENT 标志。

3.18 使用ENUM而不是VARCHAR

  ENUM 类型是非常快和紧凑的。在实际上,其保存的是TINYINT,但其外表上显示为字符串。这样一来,用这个字段来做一些选项列表变得相当的完美。

3.19 尽可能的使用 NOT NULL

  除非你有一个很特别的原因去使用 NULL 值,你应该总是让你的字段保持
NOT NULL。

3.20 把 IP 地址存成 UNSIGNED INT

  很多程序员都会创建一个 VARCHAR(15) 字段来存放字符串形式的 IP 而不是整形的 IP。如果你用整形来存放,只需要 4 个字节,并且你可以有定长的字段。而且,这会为你带来查询上的优势,尤其是当你需要使用这样的 WHERE 条件:IP between ip1 and ip2。
  我们必需要使用 UNSIGNED INT,因为 IP 地址会使用整个 32 位的无符号整
形。

3.21 固定长度的表会更快

   如果表中的所有字段都是“固定长度”的,整个表会被认为是 “static”或 “fixed-length”。固定长度的表会提高性能,因为 MySQL 搜寻得会更快一些,因为这些固定的长度是很容易计算下一个数据的偏移量的,所以读取的自然也会很快。而如果字段不是定长的,那么,每一次要找下一条的话,需要程序找到主键。
   并且,固定长度的表也更容易被缓存和重建。不过,唯一的副作用是,固定长度的字段会浪费一些空间,因为定长的字段无论你用不用,他都是要分配那么多的空间。

猜你喜欢

转载自blog.csdn.net/m0_37741420/article/details/121449217