查询优化
EXPLAIN
在MySQL中可以使用EXPLAIN查看SQL执行计划
字段 | 描述 |
---|---|
id | select识别码 |
select_type | select语句类型 |
table | 查询的表 |
type | 表的连接类型 |
possible_keys | 可使用的索引 |
key | 实际使用索引 |
key_len | 使用的索引长度 |
ref | 字段或者常量配合查询 |
rows | 执行行数 |
extra | 查询的详情 |
select_type
-
SIMPLE : 简单查询, 其中不包括连接查询和子查询
EXPLAIN SELECT * FROM book;
-
PRIMARY : 主查询(调用子查询的查询方法)
EXPLAIN SELECT * FROM (SELECT * FROM book) AS BOOK;
EXPLAIN SELECT * FROM user UNION SELECT * FROM user2; -
DERIVED : 子查询(调用子查询的子查询方法)
EXPLAIN SELECT * FROM (SELECT * FROM book) AS BOOK;
-
UNION : 连接查询的第2个或后面的查询语句
EXPLAIN SELECT * FROM user UNION SELECT * FROM user2;
-
UNION RESULT : 连接查询的结果
-
DEPENDENT UNION : UNION中的第二个或后面的SELECT语句,取决于外面的查询
-
DEPENDENT SUBQUERY : 子查询中的第1个SELECT语句,取决于外面的查询
-
SUBQUERY : 子查询中的第1个SELECT语句
EXPLAIN SELECT * FROM user WHERE id = (SELECT u_id FROM product WHERE id = ‘2’);
type
表示表的连接类型
以下的连接类型的顺序是从最佳类型到最差类型
-
system :
表只有一行记录(等于系统表)。这是 const表连接类型的一个特例
-
const :
数据表最多只有一个匹配行,因为只匹配一行数据,所以很快,常用于PRIMARY KEY或者UNIQUE索引的查询,可理解为const是最优化。
-
eq_ref :
mysql手册是这样说的:”对于每个来自于前面的表的行组合,从该表中读取一行。
这可能是最好的联接类型,除了const类型。
它用在一个索引的所有部分被联接使用并且索引是UNIQUE或PRIMARY KEY“。eq_ref可以用于使用=比较带索引的列。explain select * from user u,user2 u2 where u.id = u2.id;
// u2 为 ALL; u 为 eq_ref; u 的ref为 u2.id -
ref :
查询条件索引既不是UNIQUE也不是PRIMARY KEY的情况。ref可用于=或<或>操作符的带索引的列。
-
ref_or_null :
该联接类型如同ref,但是添加了MySQL可以专门搜索包含NULL值的行。
在解决子查询中经常使用该联接类型的优化。 -
index_merge :
该联接类型表示使用了索引合并优化方法。
在这种情况下,key列包含了使用的索引的清单,key_len包含了使用的索引的最长的关键元素。 -
unique_subquery :
该类型替换了下面形式的IN子查询的ref:
— value IN (SELECT primary_key FROM single_table WHERE some_expr)
unique_subquery是一个索引查找函数,可以完全替换子查询,效率更高。 -
index_subquery :
该联接类型类似于unique_subquery。
可以替换IN子查询,但只适合下列形式的子查询中的非唯一索引:
— value IN (SELECT key_column FROM single_table WHERE some_expr) -
range :
只检索给定范围的行,使用一个索引来选择行。
-
index :
该联接类型与ALL相同,除了只有索引树被扫描(完整的索引表扫描)。
这通常比ALL快,因为索引文件通常比数据文件小。 -
ALL :
对于每个来自于先前的表的行组合,进行完整的表扫描。(性能最差)
possible_keys
指出MySQL能使用哪个索引在该表中找到行(可多个)。
如果该列为NULL,说明没有使用索引,可以对该列创建索引来提高性能。
// 强制忽略索引
EXPLAIN SELECT * FROM user IGNORE INDEX(age) WHERE age > 10;
// 强制使用索引, 忽略默认索引
EXPLAIN SELECT * FROM user USE INDEX(age) WHERE age > 10;
key
显示MySQL**实际决定使用的键(索引)。(只能有一个)**
如果没有选择索引,键是NULL。
key_len
索引的存储长度, 字节为单位(int 4字节 长度4)
如果键是NULL,则长度为NULL。
ref
显示使用哪个列或常数与key一起从表中选择行。
rows
显示MySQL认为它执行查询时必须检查的行数
Extra
解决查询的详细信息
- Distinct:MySQL发现第1个匹配行后,停止为当前的行组合搜索更多的行。
- Not exists:MySQL能够对查询进行LEFT JOIN优化,发现1个匹配LEFT JOIN标准的行后,不再为前面的的行组合在该表内检查更多的行。
- range checked for each record (index map: #):MySQL没有发现好的可以使用的索引,但发现如果来自前面的表的列值已知,可能部分索引可以使用。
- Using filesort:MySQL需要额外的一次传递,以找出如何按排序顺序检索行。
- Using index:从只使用索引树中的信息而不需要进一步搜索读取实际的行来检索表中的列信息。
- Using temporary:为了解决查询,MySQL需要创建一个临时表来容纳结果。
- Using where:WHERE 子句用于限制哪一个行匹配下一个表或发送到客户。
- Using sort_union(…), Using union(…), Using intersect(…):这些函数说明如何为index_merge联接类型合并索引扫描。
- Using index for group-by:类似于访问表的Using index方式,Using index for group-by表示MySQL发现了一个索引,可以用来查 询GROUP BY或DISTINCT查询的所有列,而不要额外搜索硬盘访问实际的表。
索引
索引可以提供查询的速度
但并不是使用了带有索引的字段查询都会生效
like
匹配字符串的第一个字符为“%”,索引不起作用。
只有“%”不在第一个位置,索引才会生效。
联合索引的查询
MySQL可以为多个字段创建索引,一个索引可以包括16个字段。
对于联合索引,只有查询条件中使用了这些字段中第一个字段时,索引才会生效。
例:
如果id1(主键1)与id2为主键(主键2)
EXPLAIN SELECT * FROM TABLE WHERE id1 = 1 and id2 = 1; // 索引生效
EXPLAIN SELECT * FROM TABLE WHERE id1 = 1; // 索引生效
EXPLAIN SELECT * FROM TABLE WHERE id2 = 1; // 索引失效
OR
查询语句的查询条件中只有OR关键字,
且OR**前后的两个条件中的列都是索引时,索引才会生效**
否则,索引不生效。
子查询优化
子查询虽然很灵活,但是执行效率并不高。
执行子查询时,MYSQL需要创建临时表,查询完毕后再删除这些临时表,所以,子查询的速度会受到一定的影响。
优化:
可以使用连接查询(JOIN)代替子查询,连接查询时不需要建立临时表,其速度比子查询快。
数据库结构
将字段很多的表分解成多个表
对于字段较多的表,如果有些字段的使用频率很低,可以将这些字段分离出来形成新表。
因为当一个表的数据量很大时,会由于使用频率低的字段的存在而变慢。
增加中间表
对于需要经常联合查询的表,可以建立中间表以提高查询效率。
通过建立中间表,将需要通过联合查询的数据插入到中间表中,然后将原来的联合查询改为对中间表的查询。
增加冗余字段
设计数据表时应尽量遵循范式理论的规约,尽可能的减少冗余字段,让数据库设计看起来精致、优雅。但是,合理的加入冗余字段可以提高查询速度。
表的规范化程度越高,表和表之间的关系越多,需要连接查询的情况也就越多,性能也就越差。
注意:
冗余字段的值在一个表中修改了,就要想办法在其他表中更新,否则就会导致数据不一致的问题。
插入数据
插入数据时,影响插入速度的主要是索引、唯一性校验、一次插入的数据条数等。
插入数据的优化,不同的存储引擎优化手段不一样,在MySQL中常用的存储引擎有,MyISAM和InnoDB,两者的区别:
- MyISAM(默认存储引擎)
支持全文搜索,但不是事务安全的,而且不支持外键。
每张MyISAM表存放在三个文件中:
- frm 文件存放表格定义;
- 数据文件是MYD (MYData);
- 索引文件是MYI (MYIndex)。
- InnoDB
支持事务,支持回滚、崩溃恢复能力、多版本并发控制、ACID事务,支持行级锁定(InnoDB表的行锁不是绝对的,如果在执行一个SQL语句时MySQL不能确定要扫描的范围,InnoDB表同样会锁全表,如like操作时的SQL语句),以及提供与Oracle类型一致的不加锁读取方式。
InnoDB存储它的表和索引在一个表空间中,表空间可以包含数个文件。
// 修改默认引擎
set table_type=InnoDB
// 查询默认引擎
show variables like 'table_type'
// 修改表引擎
alter table tablename type=InnoDB
MyISAM管理非事务表:
它提供高速存储和检索,以及全文搜索能力。如果应用中需要执行大量的SELECT查询,那么MyISAM是更好的选择。
禁用索引
对于非空表,插入记录时,MySQL会根据表的索引对插入的记录建立索引。如果插入大量数据,建立索引会降低插入数据速度。
为了解决这个问题,可以在批量插入数据之前禁用索引,数据插入完成后再开启索引。
禁用索引的语句:
ALTER TABLE table_name DISABLE KEYS
开启索引语句:
ALTER TABLE table_name ENABLE KEYS
// 对于空表批量插入数据,则不需要进行操作,因为MyISAM引擎的表是在导入数据后才建立索引
禁用唯一性检查
唯一性校验会降低插入记录的速度,可以在插入记录之前禁用唯一性检查,插入数据完成后再开启。
禁用唯一性检查的语句:
SET UNIQUE_CHECKS = 0;
开启唯一性检查的语句:
SET UNIQUE_CHECKS = 1;
批量插入数据
批量插入数据,可以使用一条INSERT语句插入多条数据, 比一条INSERT语句一条记录效率高。
LOAD DATA INFILE
当需要批量导入数据时,使用LOAD DATA INFILE语句比INSERT语句插入速度快很多。
load data [low_priority] [local] infile "f:\\user.txt"
[replace|ignore]
into table user2
character set gbk
fields
terminated by ','
enclosed by '"'
lines
terminated by '\n'
(`username`,`sex`,`address`);
// f:\\user.txt内容
"名字1","1","地址1"
low_priority : 低优先级, 数据库空闲时上传
local : 本地上传
replace : 新行将代替有相同的唯一键值的现有行
ignore : 跳过唯一键的现有行的重复输入行
fields:terminated by : 字段分割
fields:enclosed by : 字段括起字符
lines:terminated by : 换行符
InnoDB用于事务处理应用程序,具有众多特性,包括ACID事务支持
如果应用中需要执行大量的INSERT或UPDATE操作,则应该使用InnoDB,这样可以提高多用户并发操作的性能。
禁用唯一性检查
与MyISAM一致
禁用外键检查
插入数据之前执行禁止对外键的检查,数据插入完成后再恢复,可以提供插入速度。
禁用:
SET foreign_key_checks = 0;
开启:
SET foreign_key_checks = 1;
禁止自动提交
mysql默认是自动提交的, 插入数据之前执行禁止事务的自动提交,数据插入完成后再恢复,可以提高插入速度。
禁用:
SET autocommit = 0;
开启:
SET autocommit = 1;
服务器
优化服务器硬件
需要从以下几个方面考虑:
1. 配置较大的内存。足够大的内存,是提高MySQL数据库性能的方法之一。
内存的IO比硬盘快的多,可以增加系统的缓冲区容量,使数据在内存停留的时间更长,以减少磁盘的IO。
2. 配置高速磁盘,比如SSD。
3. 合理分配磁盘IO,把磁盘IO分散到多个设备上,以减少资源的竞争,提高并行操作能力。
4. 配置多核处理器,MySQL是多线程的数据库,多处理器可以提高同时执行多个线程的能力。
优化MySQL的参数
MySQL的配置参数都在my.conf或者my.ini文件的[mysqld]组中,常用的参数如下:
- key_buffer_size : 缓存区大小;(太大导致OS频繁换页, 降低性能)
- table_cache : 同时打开表的个数(太大影响性能)
- query_cache_size : 查询缓存区大小(配合query_cache_type使用)
- sort_buffer_size : 排序缓存区大小(数值大, 排序速度快)
- read_buffer_size : 每个线程扫描时, 每个表分配缓存区大小
- read_rnd_buffer_size : 每个线程保留的缓存区大小
- innodb_buffer_pool_size : 表示InnoDB类型的表和索引的最大缓存
- max_connections : 最大连接数
- innodb_flush_log_at_trx_commit : 缓存区数据写入日志文件时间, 并将日志文件写入磁盘。
- interactive_timeout : 服务器在关闭连接前等待行动秒数
- thread_cache_size : 复用的线程的数量
- wait_timeout : 服务器在关闭一个连接时等待行动的秒数(默认28 800秒)