MySQL优化

MySQL优化相关序

为什么需要MySQL优化?

避免出现页面访问错误

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

增加数据库稳定性

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

优化用户体验

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

[图片上传中...(image-3a9d89-1589617850995-0)]

根据此表得出结论:优化最佳方式是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(不访问索引即能得到结果)

    以下为常见select_type的示例:

    mysql>desc select * from film where rating >9\G
    mysql>desc select title from film\G
    mysql>desc select * from payment where customer_id >= 300 and customer_id <= 350\G
    mysql>desc select * from payment where customer_id = 350\G
    mysql>desc select b.*, a.* from payment a, customer b where a.customer_id = b.customer_id\G 
    mysql>desc select * from film a, film_text b where a.film_id = b.film_id\G
    mysql>desc select * from (select * from customer where email = 'AARON.SELBY@sakilacustomer.org')a\G
    mysql>desc select 1 from dual where 1\G
    
    
  • 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;

检查是否开启
mysql>select @@profiling;

设置开启
mysql>set profiling = 1;

查询执行情况
mysql>show profiles;

查看具体执行ID的情况
mysql>show profile for query n;

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

1.2 索引问题

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

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

匹配全值:

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:

mysql>desc select * from payment where rental_id is null\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优化

存在但不能使用的场景

以 % 开头的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

条件索引使用函数:

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

检查索引使用情况
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:在文件中读下一行的请求数,高则意味着查询运行低效,应该添加索引补救。

1.3 简单实用的优化方式

定期分析和检查表
analyze table payment;
check table payment;

定期优化表

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表可能是独立表空间,在删除大量数据后可通过修改表引擎的方式回收空间

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

1.4 常用SQL的优化

大批量插入数据

当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;     //开启

优化INSERT语句

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

insert into test values(1, 2),(2, 3)……

LOAD DATA INFILE 比INSERT快20倍(理论)

大量数据插入实例

检测

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

内存和CPU的使用
free –m
top

准备工作

1、建表

create table t1 (id int auto_increment primary key,name varchar(255),name1 varchar(255)) engine=myisam default charset=utf8;

2、插入测试数据

mysql -uroot -p123 test < /root/10000.sql

3、复制成约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;

4、复制过程中查看服务器信息(文件大小、内存、CPU%)

5、查看数据文件

MyISAM引擎的表有三个文件

xxx.frm     是表的表结构

xxx.MYD     是表的数据

xxx.MYI     是表的索引

InnoDB引擎的表有两个文件

xxx.frm     是表的表结构

在上层data下还有一个

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

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

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

6、查看数据条数

select count(id) from t1;

7、开始查询

select count(*) from t1 where name like '王%';

查询响应时间慢长

8、为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

    完成后索引文件变大

9、再次查询 mysql>select count(*) from t1 where name like '王%'; 快很多

10、用不到索引的查询 select count() from t1 where name like '%王%'; select count() from t1 where name1 like '%王%'; 如果有索引用不到,比没索引还要慢

优化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;

不能使用索引的排序:

混合使用asc和desc
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;

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

优化 GROUP BY语句

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

优化嵌套查询(子查询)

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更加快速。

优化 WHERE OR 条件

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

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

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

优化 LIMIT 分页

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

第一种优化思路:

mysql>desc select film_id, description from film order by title limit 50,10\G

第二种优化思路:

mysql>desc select film_id, description from film where film_id > 50 order by title limit 10\G

where 代替 having

先筛选再分组

二、优化数据库表结构

2.1 优化表的数据类型

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

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

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

2.2 通过表拆分提高效率

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

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

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

2.3 逆规范化(反三范式)

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

2.4 字段类型的选取

  1. 整型
  • 手机号:bigint
  • IP地址:int
    • 使用函数INET_ATON()可将ip地址(点分十进制)转为数字
    • 使用函数INET_NTOA()可将数字再转为ip地址
    • PHP中也有相关的处理函数,为了提高效率可在PHP中处理 ip2long long2ip
  • 根据需求选择最小整数类型
  • 值得种类很少时可适当选择枚举ENUM
  1. 字符型
  • 计算varchar的最大长度
    • UTF8:varchar(21844)
    • GBK:varchar(32739)
  • char和varchar的选择
    • 变化的值如地址等用varchar
    • 固定的值如密码等用char
    • varchar要用多一个字节存储值得长度
  1. 时间类型
  2. 优先选择TIMESTAMP,它会自动更新时间

2.5 采用合适的锁机制

MySQL的锁有以下几种形式: - 表级锁:开销小,加锁快;不会出现死锁;锁定颗粒度大,发生冲突概率最高,并发度最低。MyISAM属于此类级 - 行级锁:开销大,加锁慢;会出现死锁;锁定颗粒度小,发生冲突几率低,并发最高。InnoDB属于此类级 - 页面锁:介于表级和行级之间。NDB属于此类级

mysql>lock table tbl_name read;
//以只读方式锁定表
mysql>unlock tables;
//解锁

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

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

三、系统配置调优

3.1 per_thread_buffers

read_buff_size

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

read_rnd_buffer_size

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

sort_buffer_size

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

thread_stack

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

join_buffer_size

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

binlog_cache_size

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

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

3.2 global_buffers 优化

innodb_buffer_poll_size

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

innodb_additional_mem_pool_size

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

innodb_log_buffer_size

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

key_buffer_size

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

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

3.3 Query Cache的使用

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

query_cache_type=1;     //开启

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

query_cache_size=0;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352