MySQL优化

目录

前言

本文整理一下关于MySQL的优化问题,对于sql的定位、分析、优化的一些步骤做了一些整理。


MySQL优化相关

为什么需要MySQL优化?

避免出现页面访问错误

  • 由于数据库连接timeout产生页面5xx的错误
  • 由于慢查询造成页面无法加载
  • 由于阻塞造成数据无法提交

 
增加数据库稳定性

  • 很多数据库问题都是由于低效的查询引起的

 
优化用户体验

  • 流畅页面的访问速度
  • 良好的网站功能体验

 
根据此表得出结论:优化最佳方式是SQL及索引的优化,效果好成本低,所以我们以逆向的顺序介绍优化方式

一、SQL及索引

以下所有案例都包括在MySQL官方测试库Sakila上,Sakila是一个模拟电影出租厅信息管理系统的数据库,具体请参见《SakilaMySQL样例数据库解析》

1.1 优化SQL语句的一般步骤

1. 通过show status命令了解服务器状态和SQL执行频率

通过show status命令可以提供服务器状态信息

格式: show [session|global] status [like] //session是默认参数表示当前连接,global表示全局
mysql>show status like 'com_%';             //显示各类语句执行次数,所有表
mysql>show status like 'Innodb_rows%';      //只统计Innodb表

以下几个参数便于用户了解数据库基本情况

Connections:视图连接MySQL服务的次数
Uptime:服务器工作时间
Slow_queries:慢查询次数       

2. 定位执行效率低的SQL语句

顾名思义,会将你认为慢的SQL记录下来,MySQL自带日志记录功能。

慢查询相关属性:
mysql>show variables like 'slow_query%';        //返回功能是否开启和日志存放位置
mysql>show variables like "long_query_time";    //返回慢查询的限定值单位秒(几秒算慢?)
mysql>show status like 'slow_queries';          //返回慢查询次数

开关慢查询:
【临时】重启后失效
mysql>set global slow_query_log=1;

【永久】修改配置文件 my.conf
Linux版本:
    在my.conf的[mysqld]之后添加

    slow_query_log = ON
    slow_query_log_file = slow_query.log
    long_query_time = 1                         

    //可根据具体情况修改 单位秒

WAMP版本:
    在my.ini的[wampmysqld]之后添加

    slow_query_log = ON
    slow_query_log_file = C:/wamp/bin/mysql/mysql5.7.9/kid-PC-slow.log  
    long_query_time = 1

慢查询日志主要包含内容为:

  • SQL主机信息
  • SQL执行信息
  • SQL执行时间
  • SQL内容

 
经过筛查可以发现有问题的SQL

3. 通过EXPLAIN 或 DESC 分析低效的SQL

通过以上步骤获取到效率低的SQL后可以通过EXPLAIN或者DESC命令对SQL语句进行分析

mysql>desc select sum(amount) 
from customer a,payment b 
where 1=1 and a.customer_id =\
b.customer_id and email = 'JANE.BENNETT@sakilacustomer.org'\G

对显示参数的说明:

  • select_type:表示SELECT类型

    • SIMPLE(简单表,不使用多表联查或子查询)
    • PRIMARY(主查询,即外层查询)
    • SUBQUERY(子查询中的第一个SELECT)
    • DEPENDENT SUBQUERY(子查询内层的第一个SELECT,依赖于外部查询的结果集)
    • DERIVED(子查询在from子句中,执行查询的时候会把子查询的结果集放到临时表)
    • UNION(UNION中的第二个或者后面的查询)
    • UNION RESULT(从UNION临时表获得结果集合)
  • table:输出结果集的表

  • type:表示MySQL在表中找到数据的方式或者访问类型,性能由差到优

    • ALL(全表)
    • index(索引扫描)
    • range(索引范围扫描)
    • index_merge(非主键的联合查询)
    • index_subquery(非主键子查询)
    • unique_subquery(主键子查询)
    • ref_or_null(同前面对null查询)
    • ref(使用非唯一或唯一索引的前缀扫描)
    • eq_ref(类似ref,区别使用的是唯一索引,对于每个值只有一条记录如primary key 或者 unique index)
    • const/system(单表中只有一行匹配,非常迅速)
    • NULL(不访问索引即能得到结果)
  • possible_keys:表示查询时可能用到的索引

  • key:表示实际用到的索引

  • key_len:使用到索引字段的长度

  • rows:扫描行数

  • Extra:执行情况说明,包含不适合在其他列显示但对执行计划非常重要的额外信息

    • using index(出现这个说明mysql使用了覆盖索引,避免访问了表的数据行,效率不错!)
    • using where(这说明服务器在存储引擎收到行后将进行过滤。有些where中的条件会有属于索引的列,当它读取使用索引的时候,就会被过滤,所以会出现有些where语句并没有在extra列中出现using where这么一个说明)
    • using temporary(使用了一张临时表)
    • using filesort(数据使用一个外部的索引排序)
  • filtered:在添加EXTENDED时出现(5.7后直接显示)与rows一起使用计算EXPLAIN的行,源码分析几乎无用!

EXPLAIN 后加 EXTEDNED 可显示扩展信息如:WARNGING

出现的WARNING信息多为MySQL优化语句(真正执行的语句) 可通过下面的语句查询WARNING详情:

show warnings;

4. 通过 show profile分析SQL

show profile功能可针对具体SQL的子步骤分析问题

mysql>select @@have_profiling; # 检查是否支持
+------------------+
| @@have_profiling |
+------------------+
| YES              |
+------------------+
1 row in set (0.05 sec)

mysql> select @@profiling; # 检查是否开启
+-------------+
| @@profiling |
+-------------+
|           0 |
+-------------+
1 row in set (0.05 sec)

设置开启
mysql> set profiling = 1;
Query OK, 0 rows affected (0.00 sec)

查询执行情况,
mysql> show profiles;
+----------+------------+--------------------+
| Query_ID | Duration   | Query              |
+----------+------------+--------------------+
|        1 | 0.00014000 | select @@profiling |
|        2 | 0.00062725 | select * from user |
+----------+------------+--------------------+
2 rows in set (0.07 sec)

mysql>show profile for query 2; # 查看具体执行Query_ID的情况
+--------------------------------+----------+
| Status                         | Duration |
+--------------------------------+----------+
| starting                       | 0.000013 |
| Waiting for query cache lock   | 0.000002 |
| starting                       | 0.000001 |
| checking query cache for query | 0.000275 |
| checking permissions           | 0.000008 |
| Opening tables                 | 0.000022 |
| init                           | 0.000015 |
| System lock                    | 0.000023 |
| Waiting for query cache lock   | 0.000002 |
| System lock                    | 0.000084 |
| optimizing                     | 0.000004 |
| statistics                     | 0.000014 |
| preparing                      | 0.000007 |
| executing                      | 0.000002 |
| Sending data                   | 0.000051 |
| end                            | 0.000002 |
| query end                      | 0.000005 |
| closing tables                 | 0.000005 |
| freeing items                  | 0.000003 |
| Waiting for query cache lock   | 0.000001 |
| freeing items                  | 0.000066 |
| Waiting for query cache lock   | 0.000014 |
| freeing items                  | 0.000002 |
| storing result in query cache  | 0.000002 |
| cleaning up                    | 0.000007 |
+--------------------------------+----------+
25 rows in set (0.09 sec)

5. 确定问题采取相应的优化措施

根据实际情况分析。

1.2 索引问题

索引是数据库优化中最常用的手段之一,通过索引的使用可提升SQL性能,本部分将详细讨论索引分类、存储、使用方法。

1. 索引的分类

⑴. 按类型名区分
  • PRIMARY KEY (ID)
  • UNIQUE (用户名、电话号、邮箱等唯一性)
  • INDEX (主要)
⑵. 按字段数量分
  • 单列
  • 多列 (分区使用)
⑶. 按存储结构分
  • B-Tree (主要)
  • HASH (Memory独有)
  • R-Tree (MyISAM在存储地理空间时使用)
  • Full-text (全文搜索,对中文支持不佳)

2. 如何使用

索引经典使用场景

⑴. 匹配全值
mysql>desc select * from rental where rental_date = '2005-05-25 17:22:10' and inventory_id = 373 and customer_id = 343\G
⑵. 匹配范围
mysql>desc select * from rental where customer_id >= 373 and customer_id < 400\G
⑶. 最左前缀
mysql>alter table payment add index idx_payment_date(payment_date, amount, last_update);
mysql>desc select * from payment where payment_date = '2006-02-14 15:16:03' and last_update = '2006-02-15 22:12:32'\G
mysql>desc select * from payment where amount = 3.98 and last_update = '2006-02-15 22:12:32'\G

说明:多列联合索引为 col1+col2+col3… 能用到的条件必须为col1开头, where 条件为 col2+col3则用不到

⑷. 仅搜索索引(覆盖索引扫描,不需要回表)
mysql>desc select last_update from payment where payment_date = '2006-02-14 15:16:03' and amount = 3.98\G
⑸. 匹配列前缀(多列索引): 使用多列索引的第一列
mysql>alter table film_text add index idx_title_desc_part(title(10),description(20));
mysql>desc select title from film_text where title like 'AFRICAN%'\G

说明:Extra的值为Using where 表示优化器需要通过索引回表查询数据。 另:对Full-text 的使用 MATCH(col1,col2…) AGAINST(‘STR’) 注意忽略

desc select * from film_text where match(title,description) against('CRUE')\G
⑹. 部分精确其它范围:
mysql>desc select inventory_id from rental where rental_date = '2006-02-14 15:16:03' and customer_id >= 300 and customer_id <= 400\G

说明:条件导致Using where 但查询的数据为inventory_id所以

⑺. 列为索引 col_name is null

如果WHERE子句包含声明为NOT NULL的列的col_name IS NULL条件,则该表达式将被优化掉。
如果列无论如何都可能产生NULL(例如,如果它来自LEFT JOIN右侧的表),则不会发生这种优化。

MySQL还可以优化col_name = expr OR col_name IS NULL的组合,这种形式在已解析的子查询中很常见。当使用此优化时,EXPLAIN显示ref_or_null。

这种优化可以处理任何关键部分的一个IS NULL。

mysql>desc select * from payment where rental_id is null\G
⑻. 存在但不能使用的场景
①. 以 % 开头的LIKE条件
mysql>desc select * from actor where last_name like '%NI%'\G

说明:key为NULL,使用不到索引

②. 数据类型出现隐式转换
mysql>desc select * from actor where last_name = 1\G
③. 多列索引查询条件不包括最左部分,即不满足左原则
mysql> desc select * from payment where amount = 3.98 and last_update = '2006-02-14 22:12:32'\G
④. 如果使用索引比全表扫描慢
mysql> update film_text set title = concat('S',title);
mysql> desc select * from film_text where title like 'S%'\G

说明:辨识度越高,越喜欢喜欢使用索引

⑤. 用or分开的条件
mysql>desc select * from payment where customer_id = 203 or amount = 3.96\G
⑥. 条件索引使用函数

说明:逻辑或后面没有索引就要全表扫描,前面还不如不用

⑼. ICP优化
mysql>select version();     //  >5.6 支持
mysql>desc select * from rental where rental_date = '2006-02-14 15:16:02' and customer_id >= 300 and customer_id <= 400\G           

说明:Extra中显示Using index condition,ICP优化

⑽. 检查索引使用情况
show status like 'Handler_read%';

Handler_read_first:索引中第一条被读的次数。如果较高,它建议服务器正执行大量全索引扫描。

Handler_read_key:根据键读一行的请求数。如果较高,说明查询和表的索引正确。

Handler_read_next:按照键顺序读下一行的请求数。如果你用范围约束或如果执行索引扫描来查询索引列,该值增加。

Handler_read_prev:按照键顺序读前一行的请求数。该读方法主要用于优化ORDER BY … DESC。

Handler_read_rnd:大量的都表文件。

Handler_read_rnd_next:在文件中读下一行的请求数,高则意味着查询运行低效,应该添加索引补救。

二、定期优化表

optimize table payment; 

说明:默认情况下,直接对InnoDB引擎的数据表使用OPTIMIZE TABLE,可能会显示「 Table does not support optimize, doing recreate + analyze instead」的提示信息。这个时候,我们可以用mysqld --skip-new或者mysqld --safe-mode命令来重启MySQL,以便于让其他引擎支持OPTIMIZE TABLE。

Innodb表可能是独立表空间,在删除大量数据后可通过修改表引擎的方式回收空间

注意:以上优化命令会有锁表,需在不繁忙是使用。

2.1 简单实用的优化方式

1. 定期分析和检查表

analyze table payment;
check table payment;

2.2 常用SQL的优化

1. 大批量插入数据

load data 时适当的设置可提高导入速度,对于MyISAM可通过以下方式提高导入速度。

ALTER TABLE tbl_name DISABLE KEYS;
loading the data……
ALTER TABLE tbl_name ENABLE KEYS;

开启或者关闭MyISAM的非唯一索引的更新,在导入大量数据到非空表示提高,空表默认就是先导入再创建,固不用设置。 对于InnoDB并不能提高效率,InnoDB提供以下几种方案:

  • 1.导入数据按照主键顺序

  • 2.导入前关闭唯一性校验,导入后开启

    SET UNIQUE_CHECKS = 0;  //关闭
    loading the data……
    SET UNIQUE_CHECKS = 1;  //开启
    
  • 3.关闭自动提交

    SET AUTOCOMMIT = 0//关闭
    loading the data……
    SET AUTOCOMMIT = 1;     //开启
    

2. 优化INSERT语句

大量数据添加使用多值INSERT语句代替多个INSERT单句

insert into test values(1, 2),(2, 3)……
LOAD DATA INFILE 比INSERT快20倍(理论)

3. 大量数据插入实例

⑴.检测
文件大小
watch -n1 ls -lh /usr/local/mysql/data/test
每一秒刷新一次,可动态查看文件大小变化。

内存和CPU的使用
free –m
top
⑵.准备工作
①. 建表
create table t1 (id int auto_increment primary key,name varchar(255),name1 varchar(255)) engine=myisam default charset=utf8;
②. 插入测试数据
mysql -uroot -p123 test < /root/10000.sql
③. 复制成约512万条
ALTER TABLE tbl_name DISABLE KEYS;

insert into t1 (name,name1) select name,name1 from t1;

将本表现存数据再次插入,相当于复制。
或者用单表导入:

select * from t1 into outfile '/tmp/t1.txt';

load data infile '/tmp/t1.txt' into table t1 (name,name1);

ALTER TABLE tbl_name ENABLE KEYS;
④. 复制过程中查看服务器信息(文件大小、内存、CPU%)
⑤. 查看数据文件

MyISAM引擎的表有三个文件

xxx.frm     是表的表结构

xxx.MYD     是表的数据

xxx.MYI     是表的索引

InnoDB引擎的表有两个文件

xxx.frm     是表的表结构

在上层data下还有一个

ibdata1是所有innodb表的数据,大小10M

500万条数据,自增主键的索引有51M

500万条数据,占了148M硬盘空间(只有两列姓名的数据)。

⑥. 查看数据条数
select count(id) from t1;
⑦. 开始查询
select count(*) from t1 where name like '王%';

查询响应时间慢长
⑧. 为name字段添加索引
alter table t1 add index ind_name (name);
    在创建时,系统占用内存300M,占用CPU 90%

    在索引时,会使用临时文件,以#号开头的是临时文件

    -rw-rw---- 1 mysql mysql 8.5K 07-19 22:58 #sql-d09_2.frm

    -rw-rw---- 1 mysql mysql  57M 07-19 23:06 #sql-d09_2.MYD

        //这个临时文件大小,接近原数据大小时,说明快创建完成了

    -rw-rw---- 1 mysql mysql  24M 07-19 23:06 #sql-d09_2.MYI

    完成后索引文件变大
⑨. 再次查询
mysql>select count(*) from t1 where name like '王%'; //快很多
⑩. 用不到索引的查询
select count(*) from t1 where name like '%王%'; select count(*) from t1 where name1 like '%王%'; 
如果有索引用不到,比没索引还要慢

4. 优化ORDER BY语句

⑴. MySQL有两种排序方式

第一种为索引排序,desc的Extra显示为Using index;

 
第二种对返回数据进行排序,Filesort排序(非索引都叫),可能使用磁盘空间或临时表进行排序,具体情况看服务器和数据大小。

能用到索引的排序:

select * from tbl_name order by key_part1, key_part2, ...;
select * from tbl_name key_part1 order by key_part1 desc, key_part2 desc;
select * from tbl_name order by key_part1 desc, key_part2 desc;

不能使用索引的排序:

混合使用ascdesc
select * from tbl_name order by key_part1 desc, key_part2 asc;

查询和排序条件不同
select * from tbl_name where key2=constant order by key1;   

对不同关键字使用排序
select * from tbl_name order by key1, key2;

小结:尽量减少额外排序,尽量使用索引返回有序数据。

5. 优化 GROUP BY语句

避免分组结果排序对性能的消耗可以指定 order by null

6. 优化嵌套查询(子查询)

MySQL5.5版本之后同样结果的子查询效率不及关联查询(JOIN),因为JOIN更高效不需要要建立临时表参与查询。

mysql>desc select * from customer where customer_id not in(select customer_id from payment)\G
mysql>desc select * from customer a left join payment b on a.customer_id = b.customer_id where b.customer_id is null\G

上一条查询类型为index_subquery,下一条为ref更加快速。

7. 优化 WHERE OR 条件

在对含有OR的条件查询时,OR两边的条件都必须用到索引,如果没有请添加。

mysql>desc select * from actor where actor_id=4 or last_name='DAVIS'\G

经过优化,改语句实际执行为两条分别执行的语句进行UNION。

8. 优化 LIMIT 分页

常见分页场景 “limit 1000,20” ,先排好序1020条,但仅仅返回1001-1020,代价太高。

第一种优化思路:id 筛选后再分页
mysql>select * from table where id > 500000 limit 10;

第二种优化思路:索引覆盖
mysql>select a.* from table as a
left join (select id from table where limit 500000,10) as b 
on a.id = b.id;

9. where 代替 having

先筛选再分组

三、优化数据库表结构

3.1 优化表的数据类型

使用函数 PROCEDURE ANALYSE() 提出优化建议

select * from payment procedure analyse();
select * from payment procedure analyse(16,256);

根据统计出的现有数据,可给出优化建议进行字段类型的更改。

3.2 通过表拆分提高效率

拆分方式两种,垂直拆分和水平拆分。

垂直拆分:表拆分成主列和辅列,也就是业务中常用和不常用的,在常用查询中I/O会减少但查询全部数据时需要JOIN。

 
水平拆分:表很大、本身就具有独立性(区域、时期、级别、常用与否等)时拆分。

3.3 逆规范化(反三范式)

  • 增加冗余列
  • 增加派生列
  • 重新组表(视图)
  • 分割表

3.4 字段类型的选取

1. 整型

  • 手机号:bigint
  • IP地址:int
    • 使用函数INET_ATON()可将ip地址(点分十进制)转为数字
    • 使用函数INET_NTOA()可将数字再转为ip地址
    • PHP中也有相关的处理函数,为了提高效率可在PHP中处理 ip2long long2ip
  • 根据需求选择最小整数类型
  • 值得种类很少时可适当选择枚举ENUM

2. 字符型

  • 计算varchar的最大长度
    • UTF8:varchar(21844)
    • GBK:varchar(32739)
  • char和varchar的选择
    • 变化的值如地址等用varchar
    • 固定的值如密码等用char
    • varchar要用多一个字节存储值得长度
  1. 时间类型
  2. 优先选择TIMESTAMP,它会自动更新时间

3.5 采用合适的锁机制

MySQL的锁有以下几种形式:

  • 表级锁:开销小,加锁快;不会出现死锁;锁定颗粒度大,发生冲突概率最高,并发度最低。MyISAM属于此类级
  • 行级锁:开销大,加锁慢;会出现死锁;锁定颗粒度小,发生冲突几率低,并发最高。InnoDB属于此类级
  • 页面锁:介于表级和行级之间。NDB属于此类级
mysql>lock table tbl_name read;
//以只读方式锁定表
mysql>unlock tables;
//解锁

在InnoDB表中,使用索引检索数据才会自动启用行锁,如果没有使用则表级锁

 
死锁:多方获取一个资源的锁定,InnoDB自检释放一个并回退继续完成另一个事务。不可避免,可通过调整业务逻辑降低概率。

四、系统配置调优

4.1 per_thread_buffers

1. read_buff_size

该参数用于顺序扫描,每个线程分配的缓冲区大小。每次扫描暂存在read_buffer_size中,写满或结束后返回给上层调用者,默认128KB。这个参数不易过大,一般在128-256KB即可。

2. read_rnd_buffer_size

该参数用于随机读取,不用索引会用此缓冲区暂存数据。默认256KB。这个参数不易过大,一般在128-256KB即可。

3. sort_buffer_size

如果在order by 和 group by没有使用索引导致 filesort时,为提高性能分配的缓冲区,默认为2MB。这个参数不易过大,一般在128-256KB即可,如果出现 Using filesort此缓冲区解决不了实质问题需要添加索引优化。

4. thread_stack

每个线程的堆栈大小默认192KB。如果是64位系统可设置为256KB,不要设置过大。

5. join_buffer_size

进行JOIN操作时,如果关联字段没索引会出现Using join buffer 为了提高性能设置此参数。默认128KB。这个参数不易过大,一般在128-256KB即可,如果出现 Using filesort此缓冲区解决不了实质问题需要添加索引优化。

6. binlog_cache_size

事务缓存值,设置为1-2MB比较合适,如果SQL有大事务可调整。

7. max_connections

该参数设置最大连接数,默认为100,一般设置为512-1000即可。

上面介绍了各种参数,那么此公式就是per_thread_buffers的含义:

(read_buffer_size+read_rnd_buffer_size+read_rnd_buffer_size+thread_stack+join_buffer_size+binlog_cache_size)*max_connections

4.2 global_buffers 优化

1. innodb_buffer_poll_size

InnoDB的核心参数,默认配置128MB,一般设置为MySQL服务器内存的60%~70%。

2. innodb_additional_mem_pool_size

用来存储表结构,表越多分配内存越多,如果用光则在日志中写警告,然后从操作系统借用内存,默认8M,当出现报错时则需要调整,一般可设置为16MB。

3. innodb_log_buffer_size

事务日志缓冲池,默认空间8MB,一般根据事务多少设置为16-64MB

4. key_buffer_size

此参数用来缓存MyISAM的索引,因为高版本默认用InnoDB所以这个参数64MB即可。

5. query_cache_size

缓存select语句的到的结果集

根据以上参数可计算出gloal_buffers

innodb_buffer_poll_size+innodb_additional_mem_pool_size+innodb_log_buffer_size+key_buffer_size+query_cache_size

4.3 Query Cache的使用

MySQL自带的缓存结果集功能,第一次查后缓存,再查读缓存数据。如果数据有修改则清除缓存。当有些表不常修改查询量又很大时非常有用。

query_cache_type=1;     //开启

如果环境下写操作较多不适合开启,频繁的刷新缓存会让性能下降,此时应该关闭

query_cache_size=0;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值