MySQL性能优化
为了保持生产环境中数据库的稳定性和性能,增强用户体验。同时也为了避免因数据库连接超时产生页面5xx的错误,有时候我们需要对数据库进行某些方面的优化。主要包括以下几个方面:
- SQL及索引优化
- 数据库表结构
- 数据库系统配置参数
- 操作系统及硬件
它们具体的优化效果及成本关系如下图所示:
在生产环境下,SQL及索引优化占有比较大的比重,而且效果比较明显,根据左边的箭头,越往上优化的成本越高;效果却越来越不明显。
【说明】:本实验环境基于mysql5.6.21版本。
一、SQL优化
1、如何发现有问题的SQL?
通过慢查询日志可以发现,在使用慢查询日志之前,需要设置以下变量参数:
(product)root@localhost [(none)]> show variables like 'slow_query_log'; #开启慢查询 +----------------+-------+ | Variable_name | Value | +----------------+-------+ | slow_query_log | ON | +----------------+-------+ 1 row in set (0.01 sec) (product)root@localhost [(none)]> show variables like 'long_query_time'; #记录超过多少秒的sql将记录到慢查询日志中。为0表示记录所有sql +-----------------+----------+ | Variable_name | Value | +-----------------+----------+ | long_query_time | 1.000000 | +-----------------+----------+ 1 row in set (0.00 sec) (product)root@localhost [(none)]> show variables like 'log_queries_not_using_indexes'; #记录没有使用索引的sql到日志中 +-------------------------------+-------+ | Variable_name | Value | +-------------------------------+-------+ | log_queries_not_using_indexes | ON | +-------------------------------+-------+ 1 row in set (0.00 sec)
1) 慢查询日志的存储格式?
### 开始时间
# Time: 151224 17:27:43
### 执行SQL的主机信息 # User@Host: root[root] @ localhost [] Id: 4
### SQL的执行信息 # Query_time: 0.125071 Lock_time: 0.122781 Rows_sent: 10 Rows_examined: 10 use sakila;
### 执行时间 SET timestamp=1450949263;
### SQL的内容 select * from actor limit 10;
2) 使用官方mysqldumpslow工具
[root@node1 ~]# mysqldumpslow -t 1 /data/mysql/mysql_3306/data/slow.log -a Reading mysql slow query log from /data/mysql/mysql_3306/data/slow.log Count: 1 Time=0.00s (0s) Lock=0.00s (0s) Rows=0.0 (0), 0users@0hosts # Time: 151224 17:27:43 # User@Host: root[root] @ localhost [] Id: 4 # Query_time: 0.125071 Lock_time: 0.122781 Rows_sent: 10 Rows_examined: 10 use sakila; SET timestamp=1450949263; select * from actor limit 10
官方提供的慢查询分析工具比较方便快捷,但是提供的分析数据比较有限,下面介绍一款更强大的工具
3) pt-query-digest工具
[root@node1 bin]# pt-query-digest /data/mysql/mysql_3306/data/slow.log #############################第一部分###################################### # 190ms user time, 490ms system time, 25.93M rss, 213.35M vsz # Current date: Mon Dec 28 15:00:03 2015 # Hostname: node1 # Files: /data/mysql/mysql_3306/data/slow.log # Overall: 1 total, 1 unique, 0 QPS, 0x concurrency ______________________ # Time range: all events occurred at 2015-12-24 17:27:43 # Attribute total min max avg 95% stddev median # ============ ======= ======= ======= ======= ======= ======= ======= # Exec time 125ms 125ms 125ms 125ms 125ms 0 125ms # Lock time 123ms 123ms 123ms 123ms 123ms 0 123ms # Rows sent 10 10 10 10 10 0 10 # Rows examine 10 10 10 10 10 0 10 # Query size 28 28 28 28 28 0 28
###############################第二部分#################################### # Profile # Rank Query ID Response time Calls R/Call V/M Item # ==== ================== ============= ===== ====== ===== ============ # 1 0x5665CD6BAE86EAEC 0.1251 100.0% 1 0.1251 0.00 SELECT actor
###############################第三部分#################################### # Query 1: 0 QPS, 0x concurrency, ID 0x5665CD6BAE86EAEC at byte 0 ________ # This item is included in the report because it matches --limit. # Scores: V/M = 0.00 # Time range: all events occurred at 2015-12-24 17:27:43 # Attribute pct total min max avg 95% stddev median # ============ === ======= ======= ======= ======= ======= ======= ======= # Count 100 1 # Exec time 100 125ms 125ms 125ms 125ms 125ms 0 125ms # Lock time 100 123ms 123ms 123ms 123ms 123ms 0 123ms # Rows sent 100 10 10 10 10 10 0 10 # Rows examine 100 10 10 10 10 10 0 10 # Query size 100 28 28 28 28 28 0 28 # String: # Databases sakila # Hosts localhost # Users root # Query_time distribution # 1us # 10us # 100us # 1ms # 10ms # 100ms ################################################################ # 1s # 10s+ # Tables # SHOW TABLE STATUS FROM `sakila` LIKE 'actor'\G # SHOW CREATE TABLE `sakila`.`actor`\G # EXPLAIN /*!50100 PARTITIONS*/ select * from actor limit 10\G
pt-query-digest输出内容大体分三部分,那么如何通过这个结果找到问题SQL?
- 查询次数多且查询时间长的SQL,一般pt-query-digest输出内容的前几个查询
- IO大的SQL:第三部分的Rows examine项
- 未命中索引的项:注意Rows examine和Rows sent项对比
4) explain工具
explain可以分析某个查询的执行计划,通过执行计划分析查询是否利用索引。具体的输出参数如下:
(product)root@localhost [sakila]> explain select * from actor order by last_update; +----+-------------+-------+------+---------------+------+---------+------+------+----------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+----------------+ | 1 | SIMPLE | actor | ALL | NULL | NULL | NULL | NULL | 200 | Using filesort | +----+-------------+-------+------+---------------+------+---------+------+------+----------------+ 1 row in set (0.00 sec)
- select_type:表示查询类型,有简单查询、连接查询等。
- type:重要的列,从优到差的类型为:const、eq_reg、ref、range、index、all。
- possible_keys:可能用到的索引有哪些。为空表示没有可利用的索引
- key:实际利用的索引,为空表示没有使用索引。
- key_len:索引的长度,越短越好
- ref:显示索引哪一列被使用了。常数是最佳值
- rows:扫描的行数
- extra:重点关注using_filesort和using_temporary两项。using_filesort表示用到了排序,一般在order by情境下;using_temporary表示用到额外的临时表来存储查询的数据,一般在order by和group by情况下,出现以上两个选项表示该查询需要优化了。
2、count()和max()的优化
1)Max()优化:
(product)root@localhost [sakila]> explain select max(payment_date) from payment; +----+-------------+---------+------+---------------+------+---------+------+-------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------+------+---------------+------+---------+------+-------+-------+ | 1 | SIMPLE | payment | ALL | NULL | NULL | NULL | NULL | 16088 | NULL | +----+-------------+---------+------+---------------+------+---------+------+-------+-------+ 1 row in set (0.00 sec)
########################################################################################################################################
以上payment_date字段没有索引,故进行全表扫描后获得最大值
########################################################################################################################################
下面为payment_date字段添加索引,再看看效果:
(product)root@localhost [sakila]> create index idx_payment_date on payment(payment_date); Query OK, 0 rows affected (0.23 sec) Records: 0 Duplicates: 0 Warnings: 0 (product)root@localhost [sakila]> explain select max(payment_date) from payment;+----+-------------+-------+------+---------------+------+---------+------+------+------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+------------------------------+ | 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Select tables optimized away | +----+-------------+-------+------+---------------+------+---------+------+------+------------------------------+ 1 row in set (0.00 sec)
########################################################################################################################################
不用全表扫描了,直接从mysql内部一次读取结果,不需要再优化了。
########################################################################################################################################
2)count()优化:
提到count(),一般都会有人误解,有人说count(*)比count(column)慢,下面实验说明。
- count(*)与count(column)区别
(product)root@localhost [sakila]> select * from test1;
+----+--------+
| id | name |
+----+--------+
| 1 | NULL |
| 2 | darren |
+----+--------+
(product)root@localhost [sakila]> select count(*) from test1; +----------+ | count(*) | +----------+ | 2 | +----------+ 1 row in set (0.00 sec) (product)root@localhost [sakila]> select count(name) from test1; +-------------+ | count(name) | +-------------+ | 1 | +-------------+ 1 row in set (0.00 sec)
########################################################################################################################################
如果字段的值有NULL,那么count(*)会包括该行,而count(有空值col)会忽略该行;
########################################################################################################################################
而在性能上,不带where条件的情况下,count(*)比count(column)快点:
(product)root@localhost [sakila]> select count(*) from test; +----------+ | count(*) | +----------+ | 10000000 | +----------+ 1 row in set (1.97 sec) (product)root@localhost [sakila]> select count(*) from test; +----------+ | count(*) | +----------+ | 10000000 | +----------+ 1 row in set (1.96 sec) (product)root@localhost [sakila]> select count(*) from test; +----------+ | count(*) | +----------+ | 10000000 | +----------+ 1 row in set (2.05 sec) (product)root@localhost [sakila]> select count(*) from test; +----------+ | count(*) | +----------+ | 10000000 | +----------+ 1 row in set (2.02 sec) (product)root@localhost [sakila]> select count(*) from test; +----------+ | count(*) | +----------+ | 10000000 | +----------+ 1 row in set (1.95 sec) (product)root@localhost [sakila]> select count(*) from test; +----------+ | count(*) | +----------+ | 10000000 | +----------+ 1 row in set (1.94 sec) (product)root@localhost [sakila]> select count(*) from test; +----------+ | count(*) | +----------+ | 10000000 | +----------+ 1 row in set (2.00 sec) (product)root@localhost [sakila]> select count(*) from test; +----------+ | count(*) | +----------+ | 10000000 | +----------+ 1 row in set (1.94 sec) (product)root@localhost [sakila]> select count(id) from test; +-----------+ | count(id) | +-----------+ | 10000000 | +-----------+ 1 row in set (2.19 sec) (product)root@localhost [sakila]> select count(id) from test; +-----------+ | count(id) | +-----------+ | 10000000 | +-----------+ 1 row in set (2.19 sec) (product)root@localhost [sakila]> select count(id) from test; +-----------+ | count(id) | +-----------+ | 10000000 | +-----------+ 1 row in set (2.28 sec) (product)root@localhost [sakila]> select count(id) from test; +-----------+ | count(id) | +-----------+ | 10000000 | +-----------+ 1 row in set (2.19 sec) (product)root@localhost [sakila]> select count(id) from test; +-----------+ | count(id) | +-----------+ | 10000000 | +-----------+ 1 row in set (2.26 sec) (product)root@localhost [sakila]> select count(id) from test; +-----------+ | count(id) | +-----------+ | 10000000 | +-----------+ 1 row in set (2.20 sec)
带where条件时,两者基本上差不多,可以视为无差别:
(product)root@localhost [sakila]> select count(*) from test where id<10000000; +----------+ | count(*) | +----------+ | 9999999 | +----------+ 1 row in set (2.76 sec) (product)root@localhost [sakila]> select count(*) from test where id<10000000; +----------+ | count(*) | +----------+ | 9999999 | +----------+ 1 row in set (2.77 sec) (product)root@localhost [sakila]> select count(*) from test where id<10000000; +----------+ | count(*) | +----------+ | 9999999 | +----------+ 1 row in set (2.85 sec) (product)root@localhost [sakila]> select count(*) from test where id<10000000; +----------+ | count(*) | +----------+ | 9999999 | +----------+ 1 row in set (2.74 sec) (product)root@localhost [sakila]> select count(*) from test where id<10000000; +----------+ | count(*) | +----------+ | 9999999 | +----------+ 1 row in set (2.84 sec) (product)root@localhost [sakila]> select count(id) from test where id<10000000; +-----------+ | count(id) | +-----------+ | 9999999 | +-----------+ 1 row in set (2.81 sec) (product)root@localhost [sakila]> select count(id) from test where id<10000000; +-----------+ | count(id) | +-----------+ | 9999999 | +-----------+ 1 row in set (2.72 sec) (product)root@localhost [sakila]> select count(id) from test where id<10000000; +-----------+ | count(id) | +-----------+ | 9999999 | +-----------+ 1 row in set (2.73 sec) (product)root@localhost [sakila]> select count(id) from test where id<10000000; +-----------+ | count(id) | +-----------+ | 9999999 | +-----------+ 1 row in set (2.81 sec) (product)root@localhost [sakila]> select count(id) from test where id<10000000; +-----------+ | count(id) | +-----------+ | 9999999 | +-----------+ 1 row in set (2.73 sec)
当带有where条件时,将条件写到count括号里效率较高,比一般写到where条件后性能好:
(product)root@localhost [sakila]> select count(id<10000000 or NULL) from test; +----------------------------+ | count(id<10000000 or NULL) | +----------------------------+ | 9999999 | +----------------------------+ 1 row in set (2.58 sec) (product)root@localhost [sakila]> select count(id<10000000 or NULL) from test; +----------------------------+ | count(id<10000000 or NULL) | +----------------------------+ | 9999999 | +----------------------------+ 1 row in set (2.58 sec) (product)root@localhost [sakila]> select count(id<10000000 or NULL) from test; +----------------------------+ | count(id<10000000 or NULL) | +----------------------------+ | 9999999 | +----------------------------+ 1 row in set (2.63 sec) (product)root@localhost [sakila]> select count(id<10000000 or NULL) from test; +----------------------------+ | count(id<10000000 or NULL) | +----------------------------+ | 9999999 | +----------------------------+ 1 row in set (2.56 sec) (product)root@localhost [sakila]> select count(id<10000000 or NULL) from test; +----------------------------+ | count(id<10000000 or NULL) | +----------------------------+ | 9999999 | +----------------------------+ 1 row in set (2.58 sec) (product)root@localhost [sakila]> select count(*) from test where id<10000000; +----------+ | count(*) | +----------+ | 9999999 | +----------+ 1 row in set (2.76 sec) (product)root@localhost [sakila]> select count(*) from test where id<10000000; +----------+ | count(*) | +----------+ | 9999999 | +----------+ 1 row in set (2.77 sec) (product)root@localhost [sakila]> select count(*) from test where id<10000000; +----------+ | count(*) | +----------+ | 9999999 | +----------+ 1 row in set (2.85 sec) (product)root@localhost [sakila]> select count(*) from test where id<10000000; +----------+ | count(*) | +----------+ | 9999999 | +----------+ 1 row in set (2.74 sec) (product)root@localhost [sakila]> select count(*) from test where id<10000000; +----------+ | count(*) | +----------+ | 9999999 | +----------+ 1 row in set (2.84 sec)
【总结】:
- count(*)和count(column)在性能上差别不是很大,count(*)稍微好些,它们的主要差异在于统计数据准确性上,count(*)包括null行,所以在无特殊需求下建议使用count(*)
- 如果带有where条件,建议将条件写到前面count括号中,注意加上or null,这种写法在效率上比where条件写法高效。
3、子查询的优化
(product)root@localhost [sakila]> select count(*) from book where user_id in(select id from test); #未使用索引 +----------+ | count(*) | +----------+ | 129987 | +----------+ 1 row in set (0.13 sec)
(product)root@localhost [sakila]> select count(*) from book where user_id in(select id from test); #使用索引后
+----------+
| count(*) |
+----------+
| 129987 | +
----------+
1 row in set (0.00 sec)
对于子查询优化,首先要确保用上索引,这是关键。以前常用的有两种方案,一种是转换为join连接查询,另一种是采用exsits取代in,但是听说mysql新版本正在对子查询进行优化,下面是5.6执行比较,这里不评价:
#子查询 (product)root@localhost [sakila]> select count(*) from book where user_id in(select id from test); +----------+ | count(*) | +----------+ | 129987 | +----------+ 1 row in set (0.06 sec) (product)root@localhost [sakila]> select count(*) from book where user_id in(select id from test); +----------+ | count(*) | +----------+ | 129987 | +----------+ 1 row in set (0.06 sec) (product)root@localhost [sakila]> select count(*) from book where user_id in(select id from test); +----------+ | count(*) | +----------+ | 129987 | +----------+ 1 row in set (0.07 sec) #exists查询 (product)root@localhost [sakila]> select count(*) from book a where exists (select id from test b where a.user_id=b.id ); +----------+ | count(*) | +----------+ | 129987 | +----------+ 1 row in set (0.16 sec) (product)root@localhost [sakila]> select count(*) from book a where exists (select id from test b where a.user_id=b.id ); +----------+ | count(*) | +----------+ | 129987 | +----------+ 1 row in set (0.18 sec) (product)root@localhost [sakila]> select count(*) from book a where exists (select id from test b where a.user_id=b.id ); +----------+ | count(*) | +----------+ | 129987 | +----------+ 1 row in set (0.16 sec) (product)root@localhost [sakila]> select count(*) from book a where exists (select id from test b where a.user_id=b.id ); +----------+ | count(*) | +----------+ | 129987 | +----------+ 1 row in set (0.16 sec) (product)root@localhost [sakila]> select count(*) from book a where exists (select id from test b where a.user_id=b.id ); +----------+ | count(*) | +----------+ | 129987 | +----------+ 1 row in set (0.16 sec) #连接查询 (product)root@localhost [sakila]> select count(*) from book a join test b on b.id = a.user_id; +----------+ | count(*) | +----------+ | 129987 | +----------+ 1 row in set (0.07 sec) (product)root@localhost [sakila]> select count(*) from book a join test b on b.id = a.user_id; +----------+ | count(*) | +----------+ | 129987 | +----------+ 1 row in set (0.07 sec) (product)root@localhost [sakila]> select count(*) from book a join test b on b.id = a.user_id; +----------+ | count(*) | +----------+ | 129987 | +----------+ 1 row in set (0.07 sec)
4、limit的优化
limit给分页带来了很大的方便,但是随着数据量增加,分页的性能也会越来越慢,比如下面的limit取数,同样获取10行数据,但是数据量不同的情况下,性能也是不同的:
(product)root@localhost [sakila]> select * from test order by name limit 1,10;+-------+------------+ | id | name | +-------+------------+ | 2 | darren1 | | 11 | darren10 | | 101 | darren100 | | 10006 | darren1000 | | 10005 | darren1000 | | 10004 | darren1000 | | 10003 | darren1000 | | 10002 | darren1000 | | 10001 | darren1000 | | 1001 | darren1000 | +-------+------------+ 10 rows in set (5.69 sec) (product)root@localhost [sakila]> select * from test order by name limit 10000,10; +---------+------------+ | id | name | +---------+------------+ | 1008998 | darren1008 | | 1008999 | darren1008 | | 1009000 | darren1008 | | 1009001 | darren1009 | | 1009002 | darren1009 | | 1009003 | darren1009 | | 1009004 | darren1009 | | 1009005 | darren1009 | | 1009006 | darren1009 | | 1009007 | darren1009 | +---------+------------+ 10 rows in set (38.49 sec)
(product)root@localhost [sakila]> explain select * from test order by name limit 1,10; #排序没有利用索引
+----+-------------+-------+------+---------------+------+---------+------+---------+----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+---------+----------------+
| 1 | SIMPLE | test | ALL | NULL | NULL | NULL | NULL | 9730146 | Using filesort |
+----+-------------+-------+------+---------------+------+---------+------+---------+----------------+
1 row in set (0.00 sec)
那么怎么进行优化呢?这里分两个步骤:
- order by后的字段需要利用索引,建议使用主键或索引键
这里换成主键或者索引字段排序,再来看下效果:
(product)root@localhost [sakila]> select * from test order by id limit 1,10; +----+----------+ | id | name | +----+----------+ | 2 | darren1 | | 3 | darren2 | | 4 | darren3 | | 5 | darren4 | | 6 | darren5 | | 7 | darren6 | | 8 | darren7 | | 9 | darren8 | | 10 | darren9 | | 11 | darren10 | +----+----------+ 10 rows in set (0.00 sec) (product)root@localhost [sakila]> select * from test order by id limit 10000,10; +-------+------------+ | id | name | +-------+------------+ | 10001 | darren1000 | | 10002 | darren1000 | | 10003 | darren1000 | | 10004 | darren1000 | | 10005 | darren1000 | | 10006 | darren1000 | | 10007 | darren1000 | | 10008 | darren1000 | | 10009 | darren1000 | | 10010 | darren1000 | +-------+------------+ 10 rows in set (0.00 sec)
这时看下执行计划:
(product)root@localhost [sakila]> explain select * from test order by id limit 1,10; #跟页数有关,页数比较少时扫描的行数较少 +----+-------------+-------+-------+---------------+---------+---------+------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------+---------+------+------+-------+ | 1 | SIMPLE | test | index | NULL | PRIMARY | 4 | NULL | 11 | NULL | +----+-------------+-------+-------+---------------+---------+---------+------+------+-------+ 1 row in set (0.00 sec) (product)root@localhost [sakila]> explain select * from test order by id limit 10000,10; #跟页数有关,页数比较多时扫描的行数也越来越多了,随着数据量增加,性能也肯定下降 +----+-------------+-------+-------+---------------+---------+---------+------+-------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------+---------+------+-------+-------+ | 1 | SIMPLE | test | index | NULL | PRIMARY | 4 | NULL | 10010 | NULL | +----+-------------+-------+-------+---------------+---------+---------+------+-------+-------+ 1 row in set (0.00 sec)
跟页数有关,页数比较少时扫描的行数较少;页数比较多时扫描的行数也越来越多了,随着数据量增加,性能也肯定下降;下面进一步进行优化:
- 采用子查询方式记住上次offset位置,取页的固定大小
(product)root@localhost [sakila]> select * from test order by id limit 9000000,10; +---------+------------+ | id | name | +---------+------------+ | 9000001 | darren9000 | | 9000002 | darren9000 | | 9000003 | darren9000 | | 9000004 | darren9000 | | 9000005 | darren9000 | | 9000006 | darren9000 | | 9000007 | darren9000 | | 9000008 | darren9000 | | 9000009 | darren9000 | | 9000010 | darren9000 | +---------+------------+ 10 rows in set (2.08 sec) (product)root@localhost [sakila]> select * from test order by id limit 9000000,10; +---------+------------+ | id | name | +---------+------------+ | 9000001 | darren9000 | | 9000002 | darren9000 | | 9000003 | darren9000 | | 9000004 | darren9000 | | 9000005 | darren9000 | | 9000006 | darren9000 | | 9000007 | darren9000 | | 9000008 | darren9000 | | 9000009 | darren9000 | | 9000010 | darren9000 | +---------+------------+ 10 rows in set (2.13 sec) (product)root@localhost [sakila]> select * from test order by id limit 9000000,10; +---------+------------+ | id | name | +---------+------------+ | 9000001 | darren9000 | | 9000002 | darren9000 | | 9000003 | darren9000 | | 9000004 | darren9000 | | 9000005 | darren9000 | | 9000006 | darren9000 | | 9000007 | darren9000 | | 9000008 | darren9000 | | 9000009 | darren9000 | | 9000010 | darren9000 | +---------+------------+ 10 rows in set (2.09 sec) (product)root@localhost [sakila]> select * from test order by id limit 9000000,10; +---------+------------+ | id | name | +---------+------------+ | 9000001 | darren9000 | | 9000002 | darren9000 | | 9000003 | darren9000 | | 9000004 | darren9000 | | 9000005 | darren9000 | | 9000006 | darren9000 | | 9000007 | darren9000 | | 9000008 | darren9000 | | 9000009 | darren9000 | | 9000010 | darren9000 | +---------+------------+ 10 rows in set (2.07 sec) #采用子查询方式 (product)root@localhost [sakila]> select * from test where id >=(select id from test order by id limit 9000000,1) limit 10; +---------+------------+ | id | name | +---------+------------+ | 9000001 | darren9000 | | 9000002 | darren9000 | | 9000003 | darren9000 | | 9000004 | darren9000 | | 9000005 | darren9000 | | 9000006 | darren9000 | | 9000007 | darren9000 | | 9000008 | darren9000 | | 9000009 | darren9000 | | 9000010 | darren9000 | +---------+------------+ 10 rows in set (1.77 sec) (product)root@localhost [sakila]> select * from test where id >=(select id from test order by id limit 9000000,1) limit 10; +---------+------------+ | id | name | +---------+------------+ | 9000001 | darren9000 | | 9000002 | darren9000 | | 9000003 | darren9000 | | 9000004 | darren9000 | | 9000005 | darren9000 | | 9000006 | darren9000 | | 9000007 | darren9000 | | 9000008 | darren9000 | | 9000009 | darren9000 | | 9000010 | darren9000 | +---------+------------+ 10 rows in set (1.77 sec) (product)root@localhost [sakila]> select * from test where id >=(select id from test order by id limit 9000000,1) limit 10; +---------+------------+ | id | name | +---------+------------+ | 9000001 | darren9000 | | 9000002 | darren9000 | | 9000003 | darren9000 | | 9000004 | darren9000 | | 9000005 | darren9000 | | 9000006 | darren9000 | | 9000007 | darren9000 | | 9000008 | darren9000 | | 9000009 | darren9000 | | 9000010 | darren9000 | +---------+------------+ 10 rows in set (1.77 sec) (product)root@localhost [sakila]> select * from test where id >=(select id from test order by id limit 9000000,1) limit 10; +---------+------------+ | id | name | +---------+------------+ | 9000001 | darren9000 | | 9000002 | darren9000 | | 9000003 | darren9000 | | 9000004 | darren9000 | | 9000005 | darren9000 | | 9000006 | darren9000 | | 9000007 | darren9000 | | 9000008 | darren9000 | | 9000009 | darren9000 | | 9000010 | darren9000 | +---------+------------+ 10 rows in set (1.79 sec) (product)root@localhost [sakila]> (product)root@localhost [sakila]> select * from test where id >=(select id from test order by id limit 9000000,1) limit 10; +---------+------------+ | id | name | +---------+------------+ | 9000001 | darren9000 | | 9000002 | darren9000 | | 9000003 | darren9000 | | 9000004 | darren9000 | | 9000005 | darren9000 | | 9000006 | darren9000 | | 9000007 | darren9000 | | 9000008 | darren9000 | | 9000009 | darren9000 | | 9000010 | darren9000 | +---------+------------+ 10 rows in set (1.78 sec)
效果:取900万行以后的10行,直接用limit 9000000,10执行多次,大约耗时2.10秒;而采用子查询修改后,执行多次,大约耗时1.77秒;
二、索引优化
索引优化设计内容很多,这里不多详述。
三、数据库结构优化
1、选择合适的数据类型
数据类型的选择重点在于“合适”二字,如何确定数据类型是否合适呢?
- 使用可以存下你数据的最小数据类型
- 使用简单的数据类型,如int比varchar处理简单
- 尽可能使用not null定义字段
- 尽量少用text字段,如要使用事前考虑分表
如存储ip地址,一般我们都会选择char或者varchar来存储,但是建议使用bigint来存储,使用bigint只需要8个字节,而使用varchar最多占用15字节,而且整数比较效率也高。INET_ATON()和INET_NTOA()进行ip转换。
四、系统配置优化
第三方工具https://tools.percona.com/wizard