2 存储引擎&索引&SQL优化

数据库存储引擎&索引&SQL优化二

1 MySQL的体系结构

体系结构

整个MySQL server由以下组成

Connection Pool :连接池组件
Management Services & UTILities :管理服务和工具组件
SQL Interface: SQL接口组件
Parser :查询分析器组件
Optimizer:优化器组件
Caches & Buffers :缓冲池组件
Pluggable Storage Engines :存储引擎
File System :文件系统
1)连接层
    最上层是一些客户端和链接服务,包含本地sock通信和大多数基于客户端/服务端工具实现的类似于TCP/IP的通信。
主要完成一些类似于连接处理、授权认证、及相关的安全方案。
在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。
同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。

2) 服务层
    第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询, SQL的分析和优化,部分内置函数的执行。
所有跨存储引擎的功能也在这一层实现,如过程、函数等。在该层,服务器会解析查询并创建相应的内部解析树,
并对其完成相应的优化如确定表的查询的顺序,是否利用索引等,最后生成相应的执行操作。
如果是select语句,服务器还会查询内部的缓存,如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。

3)引擎层
    存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过AP和存储引擎进行通信。
不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选取合适的存储引擎。

4) 存储层
    数据存储层,主要是将数据存储在文件系统之上,并完成与存储引擎的交互。

和其他数据库相比, MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。
主要体现在存储引擎上,插件式的存储引擎架构,将查询处理和其他的系统任务以及数据的存储提取分离。
这种架构可以根据业务的需求和实际需要选择合适的存储引擎。

2 存储引擎

2.1 概述

    和大多数的数据库不同,MySQL中有一个存储引擎的概念,针对不同的存储需求可以选择最优的存储引擎。
    存储引擎就是存储数据,建立索引,更新查询数据等等技术的实现方式。存储引擎是基于表的,而不是基于库的。所以存储引擎也可被称为表类型。
    Oracle , Sqlserver等数据库只有一种存储引擎。MySQL提供了插件式的存储引擎架构。
所以MySQL存在多种存储引擎,可以根据需要使用相应引擎,或者编写存储引擎。
    MysQL5.0支持的存储引擎包含: InnoDB、MyISAM、BDB,MEMORY, MERGE, EXAMPLE, NDB Cluster, ARCHIVE, CSV、BLACKHOLE, FEDERATED等,
其中InnoDB和BDB提供事务安全表,其他存储引擎是非事务安全表。
    可以通过指定show engines ,来查询当前数据库支持的存储引擎:

2.2 各种存储引擎的特点

下面重点介绍几种常用的存储引擎,并对比各个存储引擎之间的区别,如下表所示:

特点 InnoDB MylSAM MEMORY MERGE NDB
存储限制 64TB 没有
事务安全 支持
锁机制 行锁(适合高并发) 表锁 表锁 表锁 行锁
B树索引 支持 支持 支持 支持 支持
哈希索引 支持
全文索引 支持(5.6版本之后) 支持
群索索引 支持
数据索引 支持 支持 支持
索引缓存 支持 支持 支持 支持 支持
数据可压缩 支持
空间使用 N/A
内存使用 中等
批量插入速度
支持外键 支持

2.2.1 InnoDB

​ InnoDB存储引擎是Mysql的默认存储引擎。InnoDB存储引擎提供了具有提交、回滚、崩溃恢复能力的事务安全。但是对比MyISAM的存储引擎, InnoDB写的处理效率差一些,并且会占用更多的磁盘空间以保留数据和索引。

​ MySQL支持外键的存储引擎只有InnoDB ,在创建外键的时候,要求父表必须有对应的索引,子表在创建外键的时候,也会自动的创建对应的索引。

InnoDB存储表和索引有以下两种方式:
① 使用共享表空间存储,这种方式创建的表的表结构保存在.frm文件中,数据和索引保存在innodb_data_home_dir和innodb-data_file_path定义的表空间中,可以是多个文件。
② 使用多表空间存储,这种方式创建的表的表结构仍然存在.frm文件中,但是每个表的数据和索引单独保存在.ibd中。

2.2.2 MyISAM

MyISAM不支持事务、也不支持外键,其优势是访问的速度快,对事务的完整性没有要求或者以SELECT,,INSERT为主的应用基本上都可以使用这个引擎来创建表。有以下两个比较重要的特点:

每个MyISAM在磁盘上存储成3个文件,其文件名都和表名相同,但拓展名分别是:
.frm (存储表定义);
MYD(MYData ,存储数据) ;
MY(MYinlex,存储索引);

2.2.3 MEMORY

待补充

2.2.4 MERGE

待补充

2.3 存储引擎的选择

    在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。
对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合。以下是几种常用的存储引擎的使用环境。

    InnoDB :是Mysql的默认存储引擎,用于事务处理应用程序,支持外键。如果应用对事务的完整性有比较高的要求,
在并发条件下要求数据的一致性,数据操作除了插入和查询意外,还包含很多的更新、删除操作,
那么InnoDB存储引擎是比较合适的选择。InnoDB存储引擎除了有效的降低由于删除和更新导致的锁定,
还可以确保事务的完整提交和回滚,对于类似于计费系统或者财务系统等对数据准确性要求比较高的系统, InnoDB是最合适的选择。

    MyISAM :如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高,
那么选择这个存储引擎是非常合适的。

    MEMORY :将所有数据保存在RAM中,在需要快速定位记录和其他类似数据环境下,可以提供几块的访问。
MEMORY的缺陷就是对表的大小有限制,太大的表无法缓存在内存中,其次是要确保表的数据可以恢复,数据库异常终止后表中的数据是可以恢复的。
MEMORY表通常用于更新不太频繁的小表,用以快速得到访问结果。

    MERGE :用于将一系列等同的MyISAM表以逻辑方式组合在一起,并作为一个对象引用他们。
MERGE表的优点在于可以突破对单个MyISAM表的大小限制,并且通过将不同的表分布在多个磁盘上,可以有效的改善MERGE表的访问效率。
这对于存储诸如数据仓储等VLDB环境十分合适。 

3 优化SQL步骤

3.1 查看SQL执行的频率

show [ session | global ] status 命令可以提供服务器状态信息。
show [ session | global ] status 可以根据需要加上参数"session"或者"global"来显示session级(当前连接)的计结果和global级(自数据库上次启动至今)的统计结果。如果不写,默认使用参数是"session"。

show status like 'Com______';
show status like 'Innidb_rows_%';

参数 含义
Com_select 执行select操作的次数,一次查询累加1
Com_insert 执行INSERT操作的次数,对于批量插入的INSERT操作,只累加一次
Com_update 执行UPDATE操作的次数
Com_delete 执行DELETE操作的次数
Innodb_rows_read select查询返回的行数
Innodb_rows_inserted 执行INSERT操作插入的行数
Innodb_rows_updated 执行UPDATE操作更新的行数执行数
Innodb_rows_ deleted DELETE操作删除的行数
Connections 试图连接MySQL服务器的次数
Uptime 服务器工作时间
Slow_queries 慢查询的次数
Com_***  这些参数对于所有存储引擎的表操作都会进行累计。
Innodb_*** 这几个参数只是针对InnoDB存储引擎的,累加的算法也略有不同。

3.2 定位低效的SQL

    慢查询日志:通过慢查询日志定位那些执行效率较低的SQL语句,用-log-slow-queries[=file_name]选项启动时, 
mysqld写一个包含所有执行时间超过1ong_query_time秒的SQL语句的日志文件。

    show processlist:慢查询日志在查询结束以后才纪录,所以在应用反映执行效率出现问题的时候查询慢查询日志并不能定位问题,
可以使用show processlist命令查看当前MySQL在进行的线程,包括线程的状态、是否锁表等,可以实时地查看SQL的执行情况,同时对一些锁表操作进行优化。
    1) id列,用户登录mysq1时,系统分配的"connection_id" ,可以使用函数connection_id()查看
    2) user列,显示当前用户。如果不是root,这个命令就只显示用户权限范围的sq1语句
    3) host列,显示这个语句是从哪个ip的哪个端口上发的,可以用来跟踪出现问题语句的用户
    4) db列,显示这个进程目前连接的是哪个数据库.
    5) command列,显示当前连接的执行的命令,一般取值为休眠(sleep ) ,查询(query) ,连接(connect)等
    6) time列,显示这个状态持续的时间,单位是秒7) state列,显示使用当前连接的sq1语句的状态,很重要的列. 
    state描述的是语句执行中的某一个状态。一个sq1语句,以查询为例,可能需要经过copying to tmp table, sorting result, sending data等状态才可以完成
    8) info列,显示这个sq1语句,是判断问题语句的一个重要依据

3.3 explain分析执行计划

通过以上步骤查询到效率低的SQL语句后,可以通过EXPLAIN或者DESC命令获取MySQL如何执行SELECT语句的信息,包括在SELECT语句执行过程中表如何连接和连接的顺序。

字段 含义
id select查询的序列号,是一组数字,表示的是查询中执行select子句或者是操作表的顺序。
select_type 表示SELECT的类型,常见的取值有SIMPLE (简单表,即不使用表连接或者子查询)、PRIMARY (主查询,即外层的查询)、UNION (UNION中的第二个或者后面的查询语句)、SUBQUERY (子查询中的第一个SELECT )等。
table 输出结果集的表。
type 表示表的连接类型,性能由好到差的连接类型为( system --> const --> eq_ref --> ref --> ref_or_null --> index_merge --> index_subquery --> range --> index --> all)。
possible_keys 表示查询时,可能使用的索引。
key 表示实际使用的索引。
key_len 索引字段的长度。
rows 扫描行的数量。
extra 执行情况的说明和描述。

1 环境准备

CREATE TABLE t_role (
    id VARCHAR (32) NOT NULL,
    role_name VARCHAR (255) DEFAULT NULL, 
    role_code VARCHAR (255) DEFAULT NULL, 
    description VARCHAR (255) DEFAULT NULL, 
    PRIMARY KEY (id),
    UNIQUE KEY unique_role_name (role_name)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

CREATE TABLE t_user (
    id varchar (32) NOT NULL,
    username varchar (45) NOT NULL,
    password varchar (96) NOT NULL,
    name varchar (45) NOT NULL,
    PRIMARY KEY (id),
    UNIQUE KEY unique_user_username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE user_role (
    id INT (11) NOT NULL AUTO_INCREMENT,
    user_id VARCHAR (32) DEFAULT NULL, 
    role_id VARCHAR (32) DEFAULT NULL,
    PRIMARY KEY (id),
    KEY fk_ur_user_id (user_id),
    KEY fk_ur_role_id (role_id),
    CONSTRAINT fk_ur_role_id FOREIGN KEY (role_id) REFERENCES t_role (id) ON DELETE NO ACTION ON UPDATE NO ACTION,
    CONSTRAINT fk_ur_user_id FOREIGN KEY (user_id) REFERENCES t_user (id) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO t_user (id, username, PASSWORD, NAME) VALUES ('1' , 'super' , '123456789' , '超级管理员');
INSERT INTO t_user (id, username, PASSWORD, NAME) VALUES ('2' , 'admin' , '12345678' , '系统管理员');
INSERT INTO t_user (id, username, PASSWORD, NAME) VALUES ('3' , 'itcast' , '1234567' , 'test02');
INSERT INTO t_user (id, username, PASSWORD, NAME) VALUES ('4' , 'stul' , '123456' , '学生1');
INSERT INTO t_user (id, username, PASSWORD, NAME) VALUES ('5' , 'stu2' , '12345' , '学生2');
INSERT INTO t_user (id, username, PASSWORD, NAME) VALUES ('6' , 't1' , '1234' , '老师1');

INSERT INTO t_role (id, role_name, role_code, description) VALUES ('5' , '学生' , 'student' , '学生');
INSERT INTO t_role (id, role_name, role_code, description) VALUES ('7' , '老师' , 'teacher' , '老师');
INSERT INTO t_role (id, role_name, role_code, description) VALUES ('8' , '教学管理员', 'teachmanager' , '教学管理员');
INSERT INTO t_role (id, role_name, role_code, description) VALUES ('9' , '管理员' , 'admin' , '管理员');
INSERT INTO t_role (id, role_name, role_code, description) VALUES ('10' , '超级管理员' , 'super' , '超级管理员');

INSERT INTO user_role(id, user_id, role_id) VALUES
(NULL, '1', '5'), (NULL, '1', '7'), (NULL, '2', '8'), (NULL, '3','9'), (NULL, '4', '8'), (NULL, '5', '10');

2 explain之id

id字段是select查询的序列号,是一组数字,表示的是查询中执行select子句或者是操作表的顺序。id情况有三种:

1) id相同表示加载表的顺序是从上到下。

explain select * from t_role r, t_user u, user_role ur where r.id = ur.role_id and u.id = ur.user_id;

2) id不同id值越大,优先级越高,越先被执行。

EXPLAIN SELECT * FROM t_role WHERE id = (SELECT role_id FROM user_role WHERE user_id = (SELECT id FROM t_user WHERE username = 'stul'));

3) id有相同,也有不同,同时存在。id相同的可以认为是一组,从上往下顺序执行;在所有的组中, id的值越大,优先级越高,越先执行。

EXPLAIN SELECT * FROM t_role r , (SELECT * FROM user_role ur WHERE ur.user_id = '2') a WHERE r.id= a.role_id ;

3 explain之select_type

表示select的类型,常见取值如下:

select_type 含义
SIMPLE 简单的select查询,查询中不包含子查询或者UNION
PRIMARY 查询中若包含任何复杂的子查询,最外层查询标记为该标识
SUBQUERY 在SELECT或WHERE列表中包含了子查询
DERIVED 在FROM列表中包含的子查询,被标记为DERIVED (衍生) MYSQL会递归执行这些子查询,把结果放在临时表中
UNION 若第二个SELECT出现在UNION之后,则标记为UNION ;若UNION包含在FROM子句的子查询中,外层SELECT将被标记为DERIVED
UNIONRESULT 从UNION表获取结果的SELECT

4 explain之table

5 explain之type

type 含义
NULL MySQL不访问任何表,索引,直接返回结果表
system 只有一行记录(等于系统表),这是const类型的特例,一般不会出现
const 表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快。如将主键置于where列表中, MySQL就能将该查询转换为一个常亮。const于将"主键"或"唯一"索引的所有部分与常量值进行比较
eq_ref 类似ref,区别在于使用的是唯一索引,使用主键的关联查询,关联查询出的记录只有一条。常见于主键或唯一索引扫描
ref 非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,返回所有匹配某个单独值的所有行(多个)
range 只检索给定返回的行,使用一个索引来选择行。where之后出现between ,<,>, in等操作。
index index与ALL的区别为index类型只是遍历了索引树,通常比ALL快, ALL是遍历数据文件。
all 将遍历全表以找到匹配的行

结果值从最好到最坏是:

NULL > system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range> index > ALL

6 explain之key

possible_keys :显示可能应用在这张表的索引,一个或多个。
key :实际使用的索引,如果为NULL , 则没有使用索引。
key_len :表示索引中使用的字节数,该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好

7 explain之rows

扫描的行

8 explain之Extra

额外的信息展示

extra 含义
using filesort 说明mysal会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取,称为"文件排序"
using temporary 使用了临时表保存中间结果, MySQLE对查i结果排时使用临时表。常见于order by和group by
using index 表示相应的select操作使用了覆盖索引,避免访问表的数据行,效率不错。

3.4 show profile分析SQL

通过 SELECT @@have_profiling; 能够看到当前MySQL是否支持profile
通过 SELECT @@profiling; 可以查看当前MySQL是否开启profiling
默认profiling是关闭的,可以通过set语句在Session级别开启profiling
通过SET profiling=1; 可以开启profiling

设置好之后可以通过 show profile 查看每条SQL消耗的时间,也可以通过 show profile for query_id 查看某个SQL每个阶段消耗的时间。在获取到最消耗时间的线程状态后, MySQL支持进一步选择all,cpu, blockio, context switch, page faults 等明细类型类查看MysQL在使用什么资源上耗费了过高的时间。

3.5 trace分析优化器执行计划

MySQL5.6提供了对SQL的跟踪race,通过trace文件能够进一步了解为什么优化器选择A计划,而不是选择B计划。
打开trace ,设置格式为JSON ,并设置trace最大能够使用的内存大小,避免解析过程中因为默认内存过小而不能够完整展示。

SET optimizer_trace="enabled=on" , end_markers_in_json=on;
set optimizer_trace_max_mem_size=1000000;

执行SQL语句:
select from tb_item where id < 4;

检查 information_schema.optimizer_trace 就可以知道MySQL是如何执行SQL的:
select from information_schema.optimizer_trace\G;

4 索引的使用

4.1 索引提高查询效率

不解释

4.2 索引的使用

1 准备环境

# 建表
CREATE TABLE tb_seller (
    sellerid VARCHAR (100),
    NAME VARCHAR (100),
    nickname VARCHAR (50),
    PASSWORD VARCHAR (60),
    STATUS VARCHAR (1),
    address VARCHAR (100),
    createtime DATETIME,
    PRIMARY KEY(sellerid) 
)ENGINE=INNODB DEFAULT CHARSET=utf8mb4;


INSERT INTO tb_seller (sellerid, NAME, nickname, PASSWORD, STATUS, address, createtime)VALUES
('alibaba' , '阿里巴巴', '阿里小店' , 'e10adc3949ba59abbe56e057f20f883e' , '1' , '北京市' , '2088-01-01 12:00:00 ');
INSERT INTO tb_seller (sellerid, NAME, nickname, PASSWORD, STATUS, address, createtime)VALUES
( 'baidu' , '百度科技有限公司' , '百度小店' , 'e10adc3949ba59abbe56e057f20f883e' , '1' , '北京市' , '2088-01-01 12:00:00');
INSERT INTO tb_seller (sellerid, NAME, nickname, PASSWORD, STATUS, address, createtime)VALUES
('huawei' , '华为科技有限公司' , '华为小店' , 'el0adc3949ba59abbe56e057f20f883e' , 'o' , '北京市' , '2088-01-01 12:00:00');
INSERT INTO tb_seller (sellerid, NAME, nickname, PASSWORD, STATUS, address, createtime)VALUES
('itcast' , '传智播客教育科技有限公司' , '传智播客' , 'el0adc3949ba59abbe56e057f20f883e' , '1' , '北京市' , '2088-01-01 12:00:00');
INSERT INTO tb_seller (sellerid, NAME, nickname, PASSWORD, STATUS, address, createtime)VALUES
('itheima' , '黑马程序员' , '黑马程序员' , 'e10adc3949ba59abbe56e057f20f883e' , 'o' , '北京市' , '2088-01-01 12:00:00');
INSERT INTO tb_seller (sellerid, NAME, nickname, PASSWORD, STATUS, address, createtime)VALUES
('luoji' , '罗技科技有限公司' , '罗技小店' , 'el0adc3949ba59abbe56e057f20f883e' , '1' , '北京市' , '2088-01-01 12:00:01');
INSERT INTO tb_seller (sellerid, NAME, nickname, PASSWORD, STATUS, address, createtime)VALUES
('oppo' , 'oPpo科技有限公司' , 'OPPO官方旗舰店' , 'e10adc3949ba59abbe56e057f20f883e' , 'o', '北京市' , '2088-01-01 12:00:00');
INSERT INTO tb_seller (sellerid, NAME, nickname, PASSWORD, STATUS, address, createtime)VALUES
('ourpalm', '掌趣科技股份有限公司' , '掌趣小店' , 'e10adc3949ba59abbe56e057f20f883e' , '1' , '北京市' , '2088-01-01 12:00:00');
INSERT INTO tb_seller (sellerid, NAME, nickname, PASSWORD, STATUS, address, createtime)VALUES
('qiandu' , '千度科技' , '千度小店' , 'el0adc3949ba59abbe56e057f20f883e' , '2' , '北京市' , '2088-01-01 12:00:00');
INSERT INTO tb_seller (sellerid, NAME, nickname, PASSWORD, STATUS, address, createtime)VALUES
('sina' , '新浪科技有限公司' , '新浪官方旗舰店' , 'e10adc3949ba59abbe56e057f20f883e' , '1' , '北京市' , '2088-01-01 12:00:00');
INSERT INTO tb_seller (sellerid, NAME, nickname, PASSWORD, STATUS, address, createtime)VALUES 
('xiaomi' , '小米科技' , '小米官方旗舰店' , 'el0adc3949ba59abbe56e057f20f883e' , '1' , '西安市' , '2088-01-01 12:00:00');
INSERT INTO tb_seller (sellerid, NAME, nickname, PASSWORD, STATUS, address, createtime)VALUES
('yijia' , '宜家家居' , '宜家家居旗舰店' , 'e10adc3949ba59abbe56e057f20f883e' , '1' , '北京市' , '2088-01-01 12:00:00');

# 索引
create index idx_seller_name_sta_addr on tb_seller (name , status , address);

2 避免索引失效

1)全值匹配,对索引中所有列都指定具体值。下列情况下,索引生效,执行效率高。

explain select *  from tb_seller where name ='小米科技' and status ='1' and address ='西安市';

2)最左前缀法则,如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始,并且不跳过索引中的列。

3)出现范围查询时,右边的列不能使用索引。

4)不要在索引列上进行运算操作,索引将失效。

5)字符串不加单引号,造成索引失效。

6)尽是使用覆盖索引,避免select*

using index:             使用覆盖索引的时候就会出现
using where:             在查找使用索引的情况下,需要回表去查询所需的数据
using index condition :   在查找使用了索引,但是需要回表查询数据
using index; using where: 查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据

7)用or分割开的条件,如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到。

8)以%开头的Like模糊查询,索引失效。如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效。

9)如果MySQL评估使用索引比全表更慢,则不使用索引。

10)is NULL ,is NOT NULL 有时索引失效。

11)in 走索引, not in索引失效。

12)尽量使用复合索引,而少使用单列索引。

# 创建复合索引
create index idx_seller_name_sta_addr on tb_seller (name , status , address);
# 就相当于创建了三个索引
name
name + status
name + status + address

# 创建单列索引
create index idx_seller_name on tb_seller (name);
create index idx_seller_status on tb_seller(status);
create index idx_seller_address on tb_seller (address);

4.3索引的使用情况

show status like 'Handler_read%';
show global status  like 'Handler_read%';

Hand ler_read_first :索引中第一条被读的次数。如果较高,表示服务器正执行大量全索引扫描(这个值越低越好)
Handler_read_key :如果索引正在工作,这个值代表一个行被索引值读的次数,如果值越低,表示索引得到的性能改善不高,因为索引不经常使用(这个值越高越好)
HandTer_read_next :按照键顺序读下一行的请求数。如果你用范围约束或如果执行索引扫描来查询索引列,该值增加。
Handler_read_prev :按照键顺序读前一行的请求数。该读方法主要用于优化ORDER BY ... DESC
Hand ler_read_rnd :根据固定位置读一行的请求数。如果你正执行大量查询并需要对结果进行排序该值较高,你可能使用了大量需要MysQL扫描整个表的查询或你的连接没有正确使用键,这个值较高,意味着运行效率低,应该建立索引来补救。
Handler_read_rnd_next :在数据文件中读下一行的请求数。如果你正进行大量的表扫描,该值较高。通常说明你的表索引不正确或写入的查询|没有利用索引

5 SQL的优化

5.1 大批量插入数据

环境准备

CREATE TABLE tb_user_1 (
    id INT (11) NOT NULL AUTO_INCREMENT, 
    username VARCHAR(45) NOT NULL,
    PASSWORD VARCHAR(96) NOT NULL, 
    NAME VARCHAR(45) NOT NULL,
    birthday DATETIME DEFAULT NULL, 
    sex CHAR (1) DEFAULT NULL,
    email VARCHAR(45) DEFAULT NULL,
    phone VARCHAR(45) DEFAULT NULL,
    qq VARCHAR(32) DEFAULT NULL,
    STATUS VARCHAR(32) NOT NULL COMMENT '用户状态',
    create_time DATETIME NOT NULL,
    update_time DATETIME DEFAULT NULL,
    PRIMARY KEY (id),
    UNIQUE KEY unique_user_username(username)
)ENGINE=INNODB DEFAULT CHARSET=utf8;

CREATE TABLE tb_user_2 (
    id INT (11) NOT NULL AUTO_INCREMENT, 
    username VARCHAR(45) NOT NULL,
    PASSWORD VARCHAR(96) NOT NULL, 
    NAME VARCHAR(45) NOT NULL,
    birthday DATETIME DEFAULT NULL, 
    sex CHAR (1) DEFAULT NULL,
    email VARCHAR(45) DEFAULT NULL,
    phone VARCHAR(45) DEFAULT NULL,
    qq VARCHAR(32) DEFAULT NULL,
    STATUS VARCHAR(32) NOT NULL COMMENT '用户状态',
    create_time DATETIME NOT NULL,
    update_time DATETIME DEFAULT NULL,
    PRIMARY KEY (id),
    UNIQUE KEY unique_user_username(username)
)ENGINE=INNODB DEFAULT CHARSET=utf8;

插入数据略......

对于InnoDB类型的表,有以下几种方式可以提高导入的效率;

    1) 主键顺序插入
因为InnoB类型的表是按照主键的顺序保存的,所以将导入的数据按照主键的顺序排列,可以有效的提高导入数据的效率。
如果InnoDB表没有主键,那么系统会自动默认创建一个内部列作为主键,所以如果可以给表创建一个主键,将可以利用这点,来提高导入数据的效率。
    2)关闭唯一性校验
在导入数据前执行SET UNIQUE_CHECKS_0 ,关闭唯一性校验,在导入结束后执行SET UNIQUECHECKS=1 ,恢复唯一性校验,可以提高导入的效率。
    3)手动提交事务
如果应用使用自动提交的方式,建议在导入前执行SET AUTOCOMMIT=0,关闭自动提交,
导入结束后再执行SET AUTOCOMMIT=1 ,打开自动提交,也可以提高导入的效率。

5.2 优化insert语句

当进行数据的insert操作的时候,可以考虑采用以下几种优化方案。如果需要同时对一张表插入很多行数据时,应该尽量使用多个值表的insert语句,这种方式将大大的缩减客户端与数据库之间的连接、关闭等消耗。使得效率比分开执行的单个insert语句快。

# 示例
insert into tb_test values (3, 'Tom');
insert into tb_test values (1, 'cat');
insert into tb_test values (2, 'Jerry');

# 优化后的方案一
insert into tb_test values (1, 'Tom'),(2, 'cat'),(3, 'Jerry');

# 优化后的方案二
start transaction; # 开启事务
insert into tb_test values (1, 'Tom');
insert into tb_test values (2, 'Cat ');
insert into tb_test values (3, 'Jerry');
commit;  # 提交

# 优化后的方案三 有序提交
insert into tb_test values (1, 'Tom');
insert into tb_test values (2, 'Cat ');
insert into tb_test values (3, 'Jerry');

5.3 优化order by 语句

环境准备

CREATE TABLE emp (
    id INT(11) NOT NULL AUTO_INCREMENT,
    NAME VARCHAR(100) NOT NULL,
    age INT(3) NOT NULL,
    salary INT(11) DEFAULT NULL,
    PRIMARY KEY (id)
)ENGINE=INNODB DEFAULT CHARSET=utf8mb4;

INSERT INTO emp (id, NAME, age, salary) VALUES ('1', 'Tom', '25', '2300');
INSERT INTO emp (id, NAME, age, salary) VALUES ('2', 'Jerry', '30', '3500');
INSERT INTO emp (id, NAME, age, salary) VALUES ('3', 'Luci', '25', '2800');
INSERT INTO emp (id, NAME, age, salary) VALUES ('4', 'Jay', '36', '3500');
INSERT INTO emp (id, NAME, age, salary) VALUES ('5', 'Tom2', '21' ,'2200');
INSERT INTO emp (id, NAME, age, salary) VALUES ('6', 'Jerry2','31', '3300');
INSERT INTO emp (id, NAME, age, salary) VALUES ('7', 'Luci2', '26', '2700');
INSERT INTO emp (id, NAME, age, salary) VALUES ('8', ']ay2', '33', '3500');
INSERT INTO emp (id, NAME, age, salary) VALUES ('9', 'Tom3', '23', '2400');
INSERT INTO emp (id, NAME, age, salary) VALUES ('10', 'Jerry3', '32', '3100');
INSERT INTO emp (id, NAME, age, salary) VALUES ('11', 'Luci3', '26', '2900');
INSERT INTO emp (id, NAME, age, salary) VALUES ('12', 'Jay3', '37', '4500');


create index idx_emp_age_salary on emp (age, salary);

5.3.1 两种排序方式

    1) 第一种是通过对返回数据进行排序,也就是通常说的filesort排序,所有不是通过索引直接返回排序结果的排序都叫FileSort排序。
    2) 第二种通过有序索引顺序扫描直接返回有序数据,这种情况即为using index ,不需要额外排序,操作效率高。

了解了MySQL的排序方式,优化目标就清晰了:尽量减少额外的排序,通过索引直接返回有序数据。
where条件和Order by使用相同的索引并且Order By的顺序和索引顺序相同,并且Order by的字段都是升序,或者都是降序。
否则肯定需要额外的操作,这样就会出现Filesort。

5.3.2 优化Filesort

通过创建合适的索引,能够减少Filesort的出现,但是在某些情况下,条件限制不能让Filesort消失,
那就需要加快Filesort的排序操作。对于Filesort , MysQL有两种排序算法:
    1)两次扫描算法: MySQL4.1之前,使用该方式排序。首先根据条件取出排序字段和行指针信息,
然后在排序区sort buffer中排序,如果sort buffer不够,则在临时表temporary table中存储排序结果。
完成排序之后,再根据行指针回表读取记录,该操作可能会导致大量随机IO操作。
    2)一次扫描算法:一次性取出满足条件的所有字段,然后在排序区sort buffer中排序后直接输出结果集。
排序时内存开销较大,但是排序效率比两次扫描算法要高。
MysQL通过比较系统变量maxlength_for-sort_data的大小和Query语句取出的字段总大小,来判定是否那种排序算法,
如果maxlength_for-sort_data更大,那么使用第二种优化之后的算法;否则使用第一种。
可以适当提高sort_buffer_size和maxlength_for-sort_data系统变量,来增大排序区的大小,提高排序的效率。

5.4 优化group by语句

由于GROUP BY实际上也同样会进行排序操作,而且与ORDER BY相比, GROUP BY主要只是多了排序之后的分组操作。
当然,如果在分组的时候还使用了其他的一些聚合函数,那么还需要一些聚合函数的计算。
所以,在GROUP BY的实现过程中,与ORDER BY一样也可以利用到索引。

如果查询包含group by但是用户想要避免排序结果的消耗,则可以执行order by null 禁止排序。如下:
drop index idx_emp_age_salary on emp;
explain select age, count(*) from emp group by age;

# 优化后
EXPLAIN SELECT age, COUNT(*) FROM emp GROUP BY age ORDER BY null;

5.5 优化嵌套查询

Mysq14.1版本之后,开始支持SQL的子查询。这个技术可以使用SELECT语句来创建一个单列的查询结果,
然后把这个结果作为过滤条件用在另一个查询中。使用子查询可以一次性的完成很多逻辑上需要多个步骤才能完成的SQL操作,
同时也可以避免事务或者表锁死,并且写起来也很容易。但是,有些情况下,子查询是可以被更高效的连接(JOIN )替代。
# 示例
explain select * from t_user where id in (select user_id from user_role);
# 优化
explain select * from t_user u , user_role ur where u.id = ur.user_id;

5.6 优化or条件

对于包含OR的查询语句,如果要利用索引,则OR之间的每个条件列都必须用到索引,而且不能使用到复合索引;
如果没有索引,则应该考虑增加索引。
explain select * from  emp where id = 1 or name ='Tom';
# OR之间的每个条件列都必须用到索引
explain select * from  emp where id = 1 or age =30;
# 不能使用到复合索引
explain select * from  emp where age = 20 or salary =3500;

建议使用union替换or

explain select * from  emp where id = 1 or id =2;
这句SQL语句虽然走了索引 但是type是range

# 优化后
explain select * from emp where id = 1 union select * from emp where id = 2;

我们来比较下重要指标,发现主要差别是type和ref这两项type显示的是访问类型,是较为重要的一个指标,结果值从好到坏依次是:system > const > eq_ref > ref > fulltext > ref_or_nul1 > index_merge > unique_subquery > index_subquery > range > index> ALL。UNION语句的type值为ref,OR语句的type值为range ,可以看到这是一个很明显的差距
UNION语句的ref值为const ,OR语句的type值为null ,,const表示是常量值引用,非常快
这两项的差距就说明了UNION要优于OR

5.7 limit优化

一般分页查询时,通过创建覆盖索引能够比较好地提高性能。一个常见又非常头疼的问题就是limit 2000000,10 ,
此时需要MySQL排序前2000010记录,仅仅返回2000000-2000010的记录,其他记录丢弃,查询排序的代价非常大。

1 优化一

在索引上完成排序分页操作,最后根据主键关联回原表查询所需要的其他列内容。

2 优化二

该方案适用于主键自增的表,可以把Limit查询转换成某个位置的查询。

5.8 索引提示

1 useindex

在查询语句中表名的后面,添加use index来提供希望MySQL去参考的索引列表,就可以让MysQL不再考虑其他可用的索引。

2 ignore index

如果用户只是单纯的想让MysQL忽略一个或者多个索引,则可以使用ignore index作为hint。

3 force index

为强制MySQL使用一个特定的索引,可在查询中使用 force index作为hint。

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