目录
一、查看数据库连接情况(通过脚本获取访问次数、线程连接数、线程运行数,并计算平均值)
方式一:查看运行的线程
方式二:开启慢查询日志
二、分析工具(mysqldumpslow)
三、分析 SQL 语句
1、方式一:explain查看 SQL 执行计划情况(关联表,表查询顺序、索引使用情况等)。
2、方式二:profiling
四、优化手段:查询优化、索引使用和表结构设计
1、查询优化:
2、索引使用
3、选择合适的数据类型--数据库表结构设计
4、表的拆分
5、读写分离
五、mysql逻辑架构
1、客户端/服务端通信协议
2、查询缓存
3、查询优化
4、查询执行引擎
5、返回结果给客户端
6、性能优化建议
7、高性能策略
MySQL性能优化实践(很全面,值得收藏)
执行命令:
show status
由于返回结果太多,此处不贴出结果。其中,再返回的结果中,我们主要关注 “Queries”、“Threadsconnected” 和 “Threadsrunning” 的值,即查询次数、线程连接数和线程运行数。
我们可以通过执行如下脚本监控 MySQL 服务器运行的状态值
#!/bin/bash
while true
do
mysqladmin -uroot -p"密码" ext | awk '/Queries/{q=$4}/Threads_connected/{c=$4}/Threads_running/{r=$4}END{printf("%d %d %d\n",q,c,r)}' >> status.txt
sleep 1
done
执行该脚本 24 小时,获取 status.txt 里的内容,再次通过 awk 计算==每秒请求 MySQL 服务的次数==
awk '{q=$1-last;last=$1}{printf("%d %d %d\n",q,$2,$3)}' status.txt
复制计算好的内容到 Excel 中生成图表观察数据周期性。
这个缓存随机数的设置,厉害了
例如:
通过随机数在[3,6,9] 区间获取其中一个值作为缓存失效时间,这样分散了缓存失效时间,从而节省了一部分内存的消耗。
show processlist;
其中,返回的 State 的值是我们判断性能好坏的关键,其值出现如下内容,则该行记录的 SQL 语句需要优化:
Converting HEAP to MyISAM # 查询结果太大时,把结果放到磁盘,严重
Create tmp table #创建临时表,严重
Copying to tmp table on disk #把内存临时表复制到磁盘,严重
locked #被其他查询锁住,严重
loggin slow query #记录慢查询
Sorting result #排序
在配置文件 my.cnf 中的 [mysqld] 一行下边添加两个参数:
slowquerylog = 1 表示开启慢查询;slowquerylogfile 表示慢查询日志存放的位置;longquerytime = 2 表示查询 >=2 秒才记录日志;logqueriesnotusing_indexes = 1 记录没有使用索引的 SQL 语句
注意:slowquerylog_file 的路径不能随便写,否则 MySQL 服务器可能没有权限将日志文件写到指定的目录中。建议直接复制上文的路径
slow_query_log = 1
slow_query_log_file=/var/lib/mysql/slow-query.log
long_query_time = 2
log_queries_not_using_indexes = 1
修改保存文件后,重启 MySQL 服务。在 /var/lib/mysql/ 目录下会创建 slow-query.log 日志文件。连接 MySQL 服务端执行如下命令可以查看配置情况。
此日志中内容繁多复杂
MySQL 提供 mysqldumpslow 工具对日志进行分析。我们可以使用 mysqldumpslow --help 查看命令相关用法。
常用参数如下:
-s:排序方式,后边接着如下参数
c:访问次数
l:锁定时间
r:返回记录
t:查询时间
al:平均锁定时间
ar:平均返回记录书
at:平均查询时间
-t:返回前面多少条的数据
-g:翻遍搭配一个正则表达式,大小写不敏感
案例:
获取返回记录集最多的10个sql
mysqldumpslow -s r -t 10 /var/lib/mysql/slow-query.log
获取访问次数最多的10个sql
mysqldumpslow -s c -t 10 /var/lib/mysql/slow-query.log
获取按照时间排序的前10条里面含有左连接的查询语句
mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/slow-query.log
用法:
explain select * from category;
返回结果:
mysql> explain select * from category;
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | category | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL |
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
字段解释:
1) id:select 查询序列号。
id相同,执行顺序由上至下;
id不同,id值越大优先级越高,越先被执行
id 包含了相同和不同的情况。(该情况一般是现有2个表或者子查询和表join ,然后在和第三个表关联查询。比如:如图 ;分析结果可看出,先走id最大的2,也就是先走括号里面的查t3表的语句。走完查t3后,顺序执行,有一个,derived是衍生的意思,意思是在执行完t3查询后的s1虚表基础上,中的2,就是id为2的。最后执行的查t2表。)
2) select_type:查询数据的操作类型,其值如下:
simple:简单查询,不包含子查询或 union
primary:包含复杂的子查询,最外层查询标记为该值,也就是最后被执行的语句。
subquery:在 select 或 where 包含子查询,被标记为该值
derived:在 from 列表中包含的子查询被标记为该值DERIVED(衍生),MySQL 会递归执行这些子查询,把结果放在临时表
union:若第二个 select 出现在 union 之后,则被标记为该值。若 union 包含在 from 的子查询中,外层 select 被标记为 derived
union result:从 union 表获取结果的 select,两种UNION语句的合并
DEPENDENT SUBQUERY: 子查询中的第一个 SELECT, 取决于外面的查询. 即子查询依赖于外层查询的结果. 出现该值的时候一定要特别注意,可能需要使用join的方式优化子查询
3) table:显示该行数据是关于哪张表
当from中有子查询的时候,表名是derivedN的形式,其中 N 指向子查询,也就是explain结果中的下一列
当有union result的时候,表名是union 1,2等的形式,1,2表示参与union的query id
注意 MySQL对待这些表和普通表一样,但是这些临时表是没有任何索引的。数据量大的情况下可能会有性能问题。
4) partitions:匹配的分区
5) type:表的连接类型,其值,性能由高到底排列如下:
system:表只有一行记录,相当于系统表
const:通过索引一次就找到,只匹配一行数据
eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常用于主键或唯一索引扫描;该类型多出现在多表join场景,通过主键或者唯一键访问表
ref:非唯一性索引扫描,此类型通常出现在sql使用非唯一或非主键索引, 或者是使用最左前缀规则索引的查询,返回匹配某个单独值的所有行。用于=、< 或 > 操作符带索引的列
range:只检索给定范围的行,使用一个索引来选择行。一般使用between、>、<情况,当 type 是 range 时,ref 字段为 NULL。
index:只遍历索引树,index 类型则是扫描所有的索引记录, 而不扫描数据
ALL:全表扫描,性能最差
注:前5种情况都是理想情况的索引使用情况。通常优化至少到range级别,最好能优化到 ref
6) possible_keys:指出 MySQL 使用哪个索引在该表找到行记录。如果该值为 NULL,说明没有使用索引,可以建立索引提高性能
7) key:显示 MySQL 实际使用的索引。如果为 NULL,则没有使用索引查询
8) key_len:表示索引中使用的字节数,通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好 显示的是索引字段的最大长度,并非实际使用长度
在这里 key_len 大小的计算规则是:
一般地,key_len 等于索引列类型字节长度,例如int类型为4 bytes,bigint为8 bytes;
如果是字符串类型,还需要同时考虑字符集因素,例如:CHAR(30) UTF8则key_len至少是90 bytes;
若该列类型定义时允许NULL,其key_len还需要再加 1 bytes;
若该列类型为变长类型,例如 VARCHAR(TEXT\BLOB不允许整列创建索引,如果创建部分索引也被视为动态列类型),其key_len还需要再加 2 bytes;
9) ref:显示该表的索引字段关联了哪张表的哪个字段
10) rows:根据表统计信息及选用情况,大致估算出找到所需的记录或所需读取的行数,数值越小越好
11) filtered:返回结果的行数占读取行数的百分比,值越大越好
12) extra:包含不合适在其他列中显示但十分重要的额外信息,常见的值如下:
using filesort:说明 MySQL 会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。出现该值,应该优化 SQL
using temporary:使用了临时表保存中间结果,MySQL 在对查询结果排序时使用临时表。常见于排序 order by 和分组查询 group by。出现该值,应该优化 SQL;需要创建一个临时表来存储结果,这通常发生在对没有索引的列进行GROUP BY时,或者ORDER BY里的列不都在索引里,需要添加合适的索引
using index:表示相应的 select 操作使用了覆盖索引,避免了访问表的数据行,效率不错
using where:where 子句用于限制哪一行
using join buffer:使用连接缓存
distinct:发现第一个匹配后,停止为当前的行组合搜索更多的行
注意:出现前 2 个值,SQL 语句必须要优化。
使用 profiling 命令可以了解 SQL 语句消耗资源的详细信息(每个执行步骤的开销)。
查看 profile 开启情况
select @@profiling;
返回结果:
mysql> select @@profiling;
+-------------+
| @@profiling |
+-------------+
| 0 |
+-------------+
1 row in set, 1 warning (0.00 sec)
0 表示关闭状态,1 表示开启;在连接关闭后,profiling 状态自动设置为关闭状态。
查看执行的 SQL 列表
show profiles;
返回结果:
查询指定 ID 的执行详细信息
show profile for query Query_ID;
返回结果:
mysql> show profile for query 9;
+----------------------+----------+
| Status | Duration |
+----------------------+----------+
| starting | 0.000207 |
| checking permissions | 0.000010 |
| Opening tables | 0.000042 |
| init | 0.000050 |
| System lock | 0.000012 |
| optimizing | 0.000003 |
| statistics | 0.000011 |
| preparing | 0.000011 |
| executing | 0.000002 |
| Sending data | 0.000362 |
| end | 0.000006 |
| query end | 0.000006 |
| closing tables | 0.000006 |
| freeing items | 0.000011 |
| cleaning up | 0.000013 |
+----------------------+----------+
15 rows in set, 1 warning (0.00 sec)
每行都是状态变化的过程以及它们持续的时间。Status 这一列和 show processlist 的 State 是一致的。因此,需要优化的注意点与上文描述的一样。
获取 CPU、 Block IO 等信息
show profile block io,cpu for query Query_ID;
show profile cpu,block io,memory,swaps,context switches,source for query Query_ID;
show profile all for query Query_ID;
select时,别用*;
两表关联时,小表驱动大表;
当 B 表的数据集小于 A 表时,用 in 优化 exist;使用 in ,两表执行顺序是先查 B 表,再查 A 表
select * from A where id in (select id from B)
当 A 表的数据集小于 B 表时,用 exist 优化 in;使用 exists,两表执行顺序是先查 A 表,再查 B 表
select * from A where exists (select 1 from B where B.id = A.id)
一些情况下,可以使用连接代替子查询,因为使用 join,MySQL 不会在内存中创建临时表。
适当添加冗余字段,减少表关联。
合理使用索引。如:为排序、分组字段建立索引,避免 filesort 的出现
适合使用索引的场景
1) 主键自动创建唯一索引
2) 频繁作为查询条件的字段
3) 查询中与其他表关联的字段
4) 查询中排序的字段
5) 查询中统计或分组字段
不适合使用索引的场景
1) 频繁更新的字段
2) where 条件中用不到的字段
3) 表记录太少
4) 经常增删改的表
5) 字段的值的差异性不大或重复性高
索引创建和使用原则
1) 单表查询:哪个列作查询条件,就在该列创建索引
2) 多表查询:left join 时,索引添加到右表关联字段;right join 时,索引添加到左表关联字段????
3) 不要对索引列进行任何操作(计算、函数、类型转换)
4) 索引列中不要使用 !=,<> 非等于
5) 索引列不要为空,且不要使用 is null 或 is not null 判断
6) 索引字段是字符串类型,查询条件的值要加''单引号,避免底层类型自动转换
违背上述原则可能会导致索引失效,具体情况需要使用 explain 命令进行查看
索引失效情况
除了违背索引创建和使用原则外,如下情况也会导致索引失效:
1) 模糊查询时,以 % 开头
2) 使用 or 时,如:字段1(非索引)or 字段2(索引)会导致索引失效。
3) 使用复合索引时,不使用第一个索引列。
index(a,b,c) ,以字段 a,b,c 作为复合索引为例
1) 使用可以存下数据最小的数据类型
2) 使用简单的数据类型。int 要比 varchar 类型在mysql处理简单
3) 尽量使用 tinyint、smallint、mediumint 作为整数类型而非 int
tinyint:迷你整型,占用1个字节保存数据,能够表示256个数值
smallint:小整型,占用2个字节保存数据,能够表示65536个数值
mediumint:中整型,占用3个字节保存数据
int:标准整型,占用4个字节保存数据,42亿多
bigint:大整型,占用8个字节保存数据
4) 尽可能使用 not null 定义字段,因为 null 占用4字节空间
5) 尽量少用 text 类型,非用不可时最好考虑分表
1、性能很差,排序等操作时,就不能使用内存临时表,必须使用磁盘临时表进行。
2、TEXT或BLOB类型只能使用前缀索引,MySQL对索引字段长度是有限制的。
3、一般系统中数据库都是性能瓶颈,磁盘IO、内存大小、网络带宽、CPU等都是影响数据库的关键因素,text最大长度太长,不适合做索引,存储的内容太大,导致频繁进行磁盘IO,传输时导致网络延时,严重影响数据库性能。对于大字段一般是转化成文件就近存放,或者是放到专门的文件服务器,数据库只存文件路径
6) 尽量使用 timestamp 而非 datetime
DATETIME的特点
保存从1001 年到 9999年,精度为秒,使用8个字节把日期和时间装到格式为“YYYYMMDDHHMMSS” 的整数,整数格式在处理时不方便,此外没有区分时区。
TIMESTAMP的特点
保存1970(同 UNIX 时间戳)年到2038年的时间,精度也是秒,使用4个字节存储。MySQL提供 FROM_UNIXTIME() 将Unix时间戳转换为日期,还提供 UNIX_TIMESTAMP() 将日期转换为Unix 时间戳。时间显示与时区相关,不同的时区时间显示不同。
MySQL中使用 TIMESTAMP 而不是 DATETIME 的原因:TIMESTAMP空间消耗少,操作方便(不像DAETIME要操作整数值)。
7) 单表不要有太多字段,建议在 20 以内
当数据库中的数据非常大时,查询优化方案也不能解决查询速度慢的问题时,我们可以考虑拆分表,让每张表的数据量变小,从而提高查询效率。
1) 垂直拆分:将表中多个列分开放到不同的表中。例如用户表中一些字段经常被访问,将这些字段放在一张表中,另外一些不常用的字段放在另一张表中。插入数据时,使用事务确保两张表的数据一致性。
2) 水平拆分:按照行进行拆分。例如用户表中,使用用户ID,对用户ID取10的余数,将用户数据均匀的分配到0~9的10个用户表中。查找时也按照这个规则查询数据。
一般情况下对数据库而言都是“读多写少”。换言之,数据库的压力多数是因为大量的读取数据的操作造成的。我们可以采用数据库集群的方案,使用一个库作为主库,负责写入数据;其他库为从库,负责读取数据。这样可以缓解对数据库的访问压力。
引自:我必须得告诉大家的 MySQL 优化原理 - 掘金
“半双工”,同一时间,只能一端发送,一端接收,并且接收端接收完整的消息,不能分段接收
客户端发送一个单独的数据包给到服务端,并且数据包中的sql语句不能过长,通过max_allowed_packet
参数控制,过长会返回异常
服务端可以返回多个数据包,因为一般结果集都很大,客户端必须完整接收所有数据包,服务端才能停止发送。因此单次数据尽量不要过大,这也是查询中尽量避免使用SELECT *
以及加上LIMIT
限制的原因之一
MySQL将缓存存放在一个引用表(不要理解成table
,可以认为是类似于HashMap
的数据结构),通过一个哈希值索引,这个哈希值通过查询本身、当前要查询的数据库、客户端协议版本号等一些可能影响结果的信息计算得来。所以两个查询在任何字符上的不同(例如:空格、注释),都会导致缓存不会命中。
如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、mysql库中的系统表,其查询结果都不会被缓存。比如函数NOW()
或者CURRENT_DATE()
会因为不同的查询时间,返回不同的查询结果,再比如包含CURRENT_USER
或者CONNECION_ID()
的查询语句会因为不同的用户而返回不同的结果,将这样的查询结果缓存起来没有任何的意义。
PS,这个是扈老师提前考试的地方,吐槽一下,这样能70分可怪了
MySQL的查询缓存系统会跟踪查询中涉及的每个表,如果这些表(数据或结构)发生变化,那么和这张表相关的所有缓存数据都将失效????(那这么说,但凡有写入的表,都没有查询缓存了?)。正因为如此,在任何的写操作时,MySQL必须将对应表的所有缓存都设置为失效。如果查询缓存非常大或者碎片很多,这个操作就可能带来很大的系统消耗,甚至导致系统僵死一会儿。而且查询缓存对系统的额外消耗也不仅仅在写操作,读操作也不例外:
针对数据库设计优化:
SQL_CACHE
和SQL_NO_CACHE
来控制某个查询语句是否需要进行缓存不要轻易打开查询缓存,特别是写密集型应用。如果你实在是忍不住,可以将query_cache_type
设置为DEMAND
,这时只有加入SQL_CACHE
的查询才会走缓存,其他查询则不会,这样可以非常自由地控制哪些查询需要被缓存
多数情况下,一条查询可以有很多种执行方式,最后都返回相应的结果。优化器的作用就是找到这其中最好的执行计划
MySQL使用基于成本的优化器,它尝试预测一个查询使用某种执行计划时的成本,并选择其中成本最小的一个。在MySQL可以通过查询当前会话的last_query_cost
的值来得到其计算当前查询的成本。
mysql> select * from t_message limit 10;
...省略结果集
mysql> show status like 'last_query_cost';
+-----------------+-------------+
| Variable_name | Value |
+-----------------+-------------+
| Last_query_cost | 6391.799000 |
+-----------------+-------------+
优化策略:
MIN()
和MAX()
函数(找某列的最小值,如果该列有索引,只需要查找B+Tree索引最左端,反之则可以找到最大值,具体原理见下文) 在完成解析和优化阶段以后,MySQL会生成对应的执行计划,查询执行引擎根据执行计划给出的指令逐步执行得出结果。整个执行过程的大部分操作均是通过调用存储引擎实现的接口来完成,这些接口被称为handler API
。查询过程中的每一张表由一个handler
实例表示。实际上,MySQL在查询优化阶段就为每一张表创建了一个handler
实例,优化器可以根据这些实例的接口来获取表的相关信息,包括表的所有列名、索引统计信息等。存储引擎接口提供了非常丰富的功能,但其底层仅有几十个接口,这些接口像搭积木一样完成了一次查询的大部分操作。
查询执行的最后一个阶段就是将结果返回给客户端。即使查询不到数据,MySQL仍然会返回这个查询的相关信息,比如改查询影响到的行数以及执行时间等等。
如果查询缓存被打开且这个查询可以被缓存,MySQL也会将结果存放到缓存中。
结果集返回客户端是一个增量且逐步返回的过程。有可能MySQL在生成第一条结果时,就开始向客户端逐步返回结果集了。这样服务端就无须存储太多结果而消耗过多内存,也可以让客户端第一时间获得返回结果。需要注意的是,结果集中的每一行都会以一个满足①中所描述的通信协议的数据包发送,再通过TCP协议进行传输,在传输过程中,可能对MySQL的数据包进行缓存然后批量发送。
回头总结一下MySQL整个查询执行过程,总的来说分为6个步骤:
选择数据类型只要遵循小而简单的原则就好,越小的数据类型通常会更快,占用更少的磁盘、内存,处理时需要的CPU周期也更少。越简单的数据类型在计算时需要更少的CPU周期,比如,整型就比字符操作代价低,因而会使用整型来存储ip地址,使用DATETIME
来存储时间,而不是使用字符串
NULL
的列改为NOT NULL
不会对性能提升有多少帮助,只是如果计划在列上创建索引,就应该将该列设置为NOT NULL
。INT(11)
,没有任何卵用。INT
使用16为存储空间,那么它的表示范围已经确定,所以INT(1)
和INT(20)
对于存储和计算是相同的。UNSIGNED
表示不允许负值,大致可以使正数的上限提高一倍。比如TINYINT
存储范围是-128 ~ 127,而UNSIGNED TINYINT
存储的范围却是0 - 255。DECIMAL
数据类型。即使是在需要存储财务数据时,仍然可以使用BIGINT
。比如需要精确到万分之一,那么可以将数据乘以一百万然后使用BIGINT
存储。这样可以避免浮点数计算不准确和DECIMAL
精确计算代价高的问题。TIMESTAMP
使用4个字节存储空间,DATETIME
使用8个字节存储空间。因而,TIMESTAMP
只能表示1970 - 2038年,比DATETIME
表示的范围小得多,而且TIMESTAMP
的值因时区不同而不同。ALTER TABLE
(如果只只是在列表末尾追加元素,不需要重建表)。ALTER TABLE
非常耗时,MySQL执行大部分修改表结果操作的方法是用新的结构创建一个张空表,从旧表中查出所有的数据插入新表,然后再删除旧表。尤其当内存不足而表又很大,而且还有很大索引的情况下,耗时更久。1、MySQL不会使用索引的情况:非独立的列
“独立的列”是指索引列不能是表达式的一部分,也不能是函数的参数。比如:
select * from where id + 1 = 5复制代码
我们很容易看出其等价于 id = 4,但是MySQL无法自动解析这个表达式,使用函数是同样的道理。
2、前缀索引
如果列很长,通常可以索引开始的部分字符,这样可以有效节约索引空间,从而提高索引效率。
3、多列索引和索引顺序
在多数情况下,在多个列上建立独立的索引并不能提高查询性能。理由非常简单,MySQL不知道选择哪个索引的查询效率更好,所以在老版本,比如MySQL5.0之前就会随便选择一个列的索引,而新的版本会采用合并索引的策略。举个简单的例子,在一张电影演员表中,在actor_id和film_id两个列上都建立了独立的索引,然后有如下查询:
select film_id,actor_id from film_actor where actor_id = 1 or film_id = 1复制代码
老版本的MySQL会随机选择一个索引,但新版本做如下的优化:
select film_id,actor_id from film_actor where actor_id = 1
union all
select film_id,actor_id from film_actor where film_id = 1 and actor_id <> 1复制代码
因此explain
时如果发现有索引合并(Extra字段出现Using union
),应该好好检查一下查询和表结构是不是已经是最优的,如果查询和表都没有问题,那只能说明索引建的非常糟糕,应当慎重考虑索引是否合适,有可能一个包含所有相关列的多列索引更适合。
索引的顺序对于查询是至关重要的,很明显应该把选择性更高的字段放到索引的前面,这样通过第一个字段就可以过滤掉大多数不符合条件的数据。
索引选择性是指不重复的索引值和数据表的总记录数的比值,选择性越高查询效率越高,因为选择性越高的索引可以让MySQL在查询时过滤掉更多的行。唯一索引的选择性是1,这时最好的索引选择性,性能也是最好的。
4、避免多个范围条件
实际开发中,我们会经常使用多个范围条件,比如想查询某个时间段内登录过的用户:
select user.* from user where login_time > '2017-04-01' and age between 18 and 30;复制代码
这个查询有一个问题:它有两个范围条件,login_time列和age列,MySQL可以使用login_time列的索引或者age列的索引,但无法同时使用它们。
5、覆盖索引
如果一个索引包含或者说覆盖所有需要查询的字段的值,那么就没有必要再回表查询,这就称为覆盖索引。覆盖索引是非常有用的工具,可以极大的提高性能,好处:
6、使用索引扫描来排序
MySQL有两种方式可以生产有序的结果集,其一是对结果集进行排序的操作,其二是按照索引顺序扫描得出的结果自然是有序的。如果explain的结果中type
列的值为index
表示使用了索引扫描来做排序。
扫描索引本身很快,因为只需要从一条索引记录移动到相邻的下一条记录。但如果索引本身不能覆盖所有需要查询的列,那么就不得不每扫描一条索引记录就回表查询一次对应的行。这个读取操作基本上是随机I/O,因此按照索引顺序读取数据的速度通常要比顺序地全表扫描要慢。
在设计索引时,如果一个索引既能够满足排序,有满足查询,是最好的。
只有当索引的列顺序和ORDER BY
子句的顺序完全一致,并且所有列的排序方向也一样时,才能够使用索引来对结果做排序。如果查询需要关联多张表,则只有ORDER BY
子句引用的字段全部为第一张表时,才能使用索引做排序。ORDER BY
子句和查询的限制是一样的,都要满足最左前缀的要求(有一种情况例外,就是最左的列被指定为常数,下面是一个简单的示例),其他情况下都需要执行排序操作,而无法利用索引排序。
// 最左列为常数,索引:(date,staff_id,customer_id) select staff_id,customer_id from demo where date = '2015-06-01' order by staff_id,customer_id复制代码
7、冗余和重复索引
冗余索引是指在相同的列上按照相同的顺序创建的相同类型的索引,应当尽量避免这种索引,发现后立即删除。比如有一个索引(A,B)
,再创建索引(A)
就是冗余索引。冗余索引经常发生在为表添加新索引时,比如有人新建了索引(A,B)
,但这个索引不是扩展已有的索引(A)
。
8、删除长期未使用的索引
特定类型查询优化
优化COUNT()查询
COUNT()
可能是被大家误解最多的函数了,它有两种不同的作用,其一是统计某个列值的数量,其二是统计行数。统计列值时,要求列值是非空的,它不会统计NULL。如果确认括号中的表达式不可能为空时,实际上就是在统计行数。最简单的就是当使用COUNT(*)
时,并不是我们所想象的那样扩展成所有的列,实际上,它会忽略所有的列而直接统计所有的行数。
不精确的COUNT
值,可以用近似值来代替,EXPLAIN出来的行数就是一个不错的近似值,而且执行EXPLAIN并不需要真正地去执行查询,所以成本非常低。通常来说,执行COUNT()
都需要扫描大量的行才能获取到精确的数据,因此很难优化,MySQL层面还能做得也就只有覆盖索引了。如果不还能解决问题,只有从架构层面解决了,比如添加汇总表,或者使用redis这样的外部缓存系统。
优化关联查询
在大数据场景下,表与表之间通过一个冗余字段来关联,要比直接使用JOIN
有更好的性能。
要理解优化关联查询的第一个技巧,就需要理解MySQL是如何执行关联查询的。当前MySQL关联执行的策略非常简单,它对任何的关联都执行嵌套循环关联操作,即先在一个表中循环取出单条数据,然后在嵌套循环到下一个表中寻找匹配的行,依次下去,直到找到所有表中匹配的行为为止。然后根据各个表匹配的行,返回查询中需要的各个列。
太抽象了?以上面的示例来说明,比如有这样的一个查询:
SELECT A.xx,B.yy
FROM A INNER JOIN B USING(c)
WHERE A.xx IN (5,6)复制代码
假设MySQL按照查询中的关联顺序A、B来进行关联操作,那么可以用下面的伪代码表示MySQL如何完成这个查询:
outer_iterator = SELECT A.xx,A.c FROM A WHERE A.xx IN (5,6);
outer_row = outer_iterator.next;
while(outer_row) {
inner_iterator = SELECT B.yy FROM B WHERE B.c = outer_row.c;
inner_row = inner_iterator.next;
while(inner_row) {
output[inner_row.yy,outer_row.xx];
inner_row = inner_iterator.next;
}
outer_row = outer_iterator.next;
}复制代码
可以看到,最外层的查询是根据A.xx
列来查询的,A.c
上如果有索引的话,整个关联查询也不会使用。再看内层的查询,很明显B.c
上如果有索引的话,能够加速查询,因此只需要在关联顺序中的第二张表的相应列上创建索引即可。
优化LIMIT分页
当需要分页操作时,通常会使用LIMIT
加上偏移量的办法实现,同时加上合适的ORDER BY
字句。如果有对应的索引,通常效率会不错,否则,MySQL需要做大量的文件排序操作。
一个常见的问题是当偏移量非常大的时候,比如:LIMIT 10000 20
这样的查询,MySQL需要查询10020条记录然后只返回20条记录,前面的10000条都将被抛弃,这样的代价非常高。
优化这种查询一个最简单的办法就是尽可能的使用覆盖索引扫描,而不是查询所有的列。然后根据需要做一次关联查询再返回所有的列。对于偏移量很大时,这样做的效率会提升非常大。考虑下面的查询:
SELECT film_id,description FROM film ORDER BY title LIMIT 50,5;复制代码
如果这张表非常大,那么这个查询最好改成下面的样子:
SELECT film.film_id,film.description
FROM film INNER JOIN (
SELECT film_id FROM film ORDER BY title LIMIT 50,5
) AS tmp USING(film_id);复制代码
这里的延迟关联将大大提升查询效率,让MySQL扫描尽可能少的页面,获取需要访问的记录后在根据关联列回原表查询所需要的列。
有时候如果可以使用书签记录上次取数据的位置(这个厉害了********),那么下次就可以直接从该书签记录的位置开始扫描,这样就可以避免使用OFFSET
,比如下面的查询:
SELECT id FROM t LIMIT 10000, 10;
改为:
SELECT id FROM t WHERE id > 10000 LIMIT 10;复制代码
其他优化的办法还包括使用预先计算的汇总表,或者关联到一个冗余表,冗余表中只包含主键列和需要做排序的列。
优化UNION
MySQL处理UNION
的策略是先创建临时表,然后再把各个查询结果插入到临时表中,最后再来做查询。因此很多优化策略在UNION
查询中都没有办法很好的时候。经常需要手动将WHERE
、LIMIT
、ORDER BY
等字句“下推”到各个子查询中,以便优化器可以充分利用这些条件先优化。
除非确实需要服务器去重,否则就一定要使用UNION ALL
,如果没有ALL
关键字,MySQL会给临时表加上DISTINCT
选项,这会导致整个临时表的数据做唯一性检查,这样做的代价非常高。当然即使使用ALL关键字,MySQL总是将结果放入临时表,然后再读出,再返回给客户端。虽然很多时候没有这个必要,比如有时候可以直接把每个子查询的结果返回给客户端
UNION和UNION ALL区别:
UNION会去重,会排序
UNION ALL不会去重,不会排序
RR(可重复读)并且insert普通索引(不唯一)
当出现using filesort (引用外部索引,没走本身索引)、using temporary(结果存到临时表,并且对临时表进行order by或者group by时没有走索引)时,需要优化
RR解决了不可重复和幻读的问题,用间隙锁解决幻读问题
RC,在where条件时,不满足过滤条件的行会释放掉锁,不满足两段锁原则,RR不满足拖累条件的数据也不会释放锁,索引RC并发比RR好