1 使用explain进行分析
比较重要的字段有:
-
select_type : 查询类型,有简单查询、联合查询、子查询等
类型 描述 SIMPLE 简单SELECT,不使用UNION或子查询等 PRIMARY 查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY UNION UNION中的第二个或后面的SELECT语句 DEPENDENT UNION UNION中的第二个或后面的SELECT语句,取决于外面的查询 UNION RESULT UNION的结果 SUBQUERY 子查询中的第一个SELECT DEPENDENT SUBQUERY 子查询中的第一个SELECT,取决于外面的查询 DERIVED 派生表的SELECT,FROM子句的子查询 UNCACHEABLE SUBQUERY 一个子查询的结果不能被缓存,必须重新评估外链接的第一行 -
key : 显示MySQL实际决定使用的键(索引),如果没有选择索引,键是NULL。
要想强制MySQL使用或忽视possible_keys列中的索引,在查询中使用FORCE INDEX、USE INDEX或者IGNORE INDEX,比如:
sqlSELECT name FROM xxx force index(idx_port) WHERE port=80;
-
rows : 扫描的行数
-
type: 访问类型
表示MySQL在表中找到所需行的方式,又称"访问类型"。常用的类型有:ALL, index, range, ref, eq_ref, const, system, NULL(从左到右,性能从差到好)。
- ALL:Full Table Scan, MySQL将遍历全表以找到匹配的行
- index:Full Index Scan,index与ALL区别为index类型只遍历索引树
- range:只检索给定范围的行,使用一个索引来选择行
- ref:表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
- eq_ref: 类似ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用primary key或者 unique key作为关联条件
- const、system: 当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于where列表中,MySQL就能将该查询转换为一个常量,system是const类型的特例,当查询的表只有一行的情况下,使用system
- NULL: MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成。
-
extra
关于MYSQL如何解析查询的额外信息。效率最低的是Using temporary和Using filesort,意味着MYSQL根本不能使用索引,所以检索会很慢。 该列包含MySQL解决查询的详细信息,有以下几种情况:
- Using index condition:
- Using where:列数据是从仅仅使用了索引中的信息而没有读取实际的行动的表返回的,这发生在对表的全部的请求列都是同一个索引的部分的时候,表示mysql服务器将在存储引擎检索行后再进行过滤
- Using temporary:表示MySQL需要使用临时表来存储结果集,常见于排序和分组查询
- Using filesort:MySQL中无法利用索引完成的排序操作称为"文件排序"
- Using join buffer:改值强调了在获取连接条件时没有使用索引,并且需要连接缓冲区来存储中间结果。如果出现了这个值,那应该注意,根据查询的具体情况可能需要添加索引来改进能。
- Impossible where:这个值强调了where语句会导致没有符合条件的行。
- Select tables optimized away:这个值意味着仅通过使用索引,优化器可能仅从聚合函数结果中返回一行
2 优化数据访问
2.1. 减少请求的数据量
- 只返回必要的列: 最好不要使用 SELECT * 语句。
- 只返回必要的行: 使用 LIMIT 语句来限制返回的数据。
- 缓存重复查询的数据: 使用缓存可以避免在数据库中进行查询,特别在要查询的数据经常被重复查询时,缓存带来的查询性能提升将会是非常明显的。
2.2. 减少服务器端扫描的行数
最有效的方式是使用索引来覆盖查询。
3 合理使用索引
索引是提高数据库查询速度最重要的方法之一,使用索引的三大注意事项包括:
- 使用多列索引必须满足最左匹配
- LIKE关键字匹配'%'开头的字符串,不会使用索引
- OR关键字的两个字段必须都是用了索引,该查询才会使用索引
4 重构查询方式
4.1. 切分大查询
一个大查询如果一次性执行的话,可能一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但重要的查询。
sql
DELEFT FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH);
rows_affected = 0
do {
rows_affected = do_query(
"DELETE FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH) LIMIT 10000")
} while rows_affected > 0
4.2. 分解大连接查询
将一个大连接查询分解成对每一个表进行一次单表查询,然后将结果在应用程序中进行关联,这样做的好处有:
- 让缓存更高效。对于连接查询,如果其中一个表发生变化,那么整个查询缓存就无法使用。而分解后的多个查询,即使其中一个表发生变化,对其它表的查询缓存依然可以使用。
- 分解成多个单表查询,这些单表查询的缓存结果更可能被其它查询使用到,从而减少冗余记录的查询。
- 减少锁竞争;
- 在应用层进行连接,可以更容易对数据库进行拆分,从而更容易做到高性能和可伸缩。
- 查询本身效率也可能会有所提升。例如下面的例子中,使用 IN() 代替连接查询,可以让 MySQL 按照 ID 顺序进行查询,这可能比随机的连接要更高效。
sql
SELECT * FROM tab
JOIN tag_post ON tag_post.tag_id=tag.id
JOIN post ON tag_post.post_id=post.id
WHERE tag.tag='mysql';
SELECT * FROM tag WHERE tag='mysql';
SELECT * FROM tag_post WHERE tag_id=1234;
SELECT * FROM post WHERE post.id IN (123,456,567,9098,8904);
4.3. 优化子查询
在MySQL中,尽量使用JOIN来代替子查询。因为子查询需要嵌套查询,嵌套查询时会建立一张临时表,临时表的建立和删除都会有较大的系统开销,而连接查询不会创建临时表,因此效率比嵌套子查询高。
4.4. 反范式设计
对于一些数据表设计冗余字段,这样在查询时,不需要连表查询,提升性能
具体实现:
-
创建订单表,包含用户的冗余信息:
sqlCREATE TABLE orders ( order_id BIGINT PRIMARY KEY, user_id INT, username VARCHAR(50), -- 冗余字段 email VARCHAR(100), -- 冗余字段 product_id INT, quantity INT, order_date DATETIME, FOREIGN KEY (user_id) REFERENCES users(user_id) );
-
在插入新订单时,将用户信息冗余存储到订单表中:
sqlINSERT INTO orders (order_id, user_id, username, email, product_id, quantity, order_date) SELECT nextval('order_seq'), u.user_id, u.username, u.email, p.product_id, 2, NOW() FROM users u JOIN products p ON u.user_id = p.user_id WHERE u.user_id = 12345;
-
在查询订单时,无需再联接用户表:
sqlSELECT order_id, username, email, product_id, quantity, order_date FROM orders WHERE user_id = 12345;
优点:查询订单时无需再联接用户表,显著提高了查询的性能。
缺点:当用户信息变化时,必须同步更新订单表,增加了数据一致性管理的复杂性。