【数据库】2、索引、调优、explain(尚硅谷笔记)

尚硅谷JAVA研究院

版本v1.1

第 1 章 MySQL 简介

1.什么是 Mysql

  • MySQL 是一个关系型数据库管理系统, 由瑞典 MySQL AB 公司开发, 目前属于 Oracle 公司。
  • Mysql 是开源的, 可以定制的, 采用了 GPL 协议, 你可以修改源码来开发自己的 Mysql 系统。
  • MySQL 使用标准的 SQL 数据语言形式。
  • Mysql 可以允许于多个系统上, 并且支持多种语言。 这些编程语言包括 C、 C++、 Python、 Java、 Perl、 PHP、Eiffel、 Ruby 和 Tcl 等。
  • MySQL 支持大型数据库, 支持 5000 万条记录的数据仓库, 32 位系统表文件最大可支持 4GB, 64 位系统支持最大的表文件为 8TB.

2.在 Linux 上安装mysql

2.1 准备工作

2.1.1 检查当前系统是否安装过 Mysql

(1) CentOS6 环境下
命令: rpm -qa|grep mysql
默认 Linux 在安装的时候, 自带了 mysql 相关的组件。
先卸载系统自带的 mysql, 执行卸载命令 rpm -e --nodeps mysql-libs
(1) CentOS7 环境下
命令: rpm -qa|grep mariadb

默认 Linux(CentOS7) 在安装的时候, 自带了 mariadb(mysql 完全开源版本)相关的组件。
先卸载系统自带的 mariadb, 执行卸载命令 rpm -e --nodeps mariadb-libs

2.1.2 检查/tmp 文件夹权限

查看/tmp 文件夹权限:

赋予其最大权限

chmod -R 777 /tmp

2.2 Mysql 的安装

安装的版本是 mysql 5.5, 官网下载地址: http://dev.mysql.com/downloads/mysql/

①将 rpm 安装包拷贝到 opt 目录下
②在安装目录下执行 rpm 安装
rpm -ivh MySQL-client-5.5.54-1.linux2.6.x86_64.rpm
rpm -ivh MySQL-server-5.5.54-1.linux2.6.x86_64.rpm

装完成后, 出现如下警告, 需要为软件设置 root 用户的密码。

③查看是否安装成功: mysqladmin --version
或者也可以通过 rpm 命令来查看:

④设置用户和密码: mysqladmin –u root password ‘xxxxxx’

如果提示“GPG keys…”安装失败,解决方案:rpm -ivh rpm软件名 --force --nodoeps

2.3 Mysql 服务

2.3.1 Mysql 服务的启动和停止

查看状态: service mysql status
启动服务: service mysql start  
停止服务: service mysql stop
重启服务: service mysql restart

启动之后, 查看进程

ps -ef|grep mysql

2.3.2 Mysql 的安装位置

参数 路径 解释 备注
–datadir /var/lib/mysql/ mysql 数据库文件的存放路径
–basedir /usr/bin 相关命令目录 mysqladmin mysqldump 等命令
–plugin-dir /usr/lib64/mysql/plugin mysql 插件存放路径
–log-error /var/lib/mysql/jack.atguigu.err mysql 错误日志路径
–pid-file /var/lib/mysql/jack.atguigu.pid 进程 pid 文件
–socket /var/lib/mysql/mysql.sock 本地连接时用的 unix 套接字文件
/usr/share/mysql 配置文件目录 mysql 脚本及配置文件
/etc/init.d/mysql 服务启停相关脚本

2.3.3 Mysql 服务的自启动

Mysql 服务是开机自动启动的!

在计算机reboot后 登陆MySQL :  mysql
可能会报错:   "/var/lib/mysql/mysql.sock不存在"  
-- 原因:是Mysql服务没有启动
1.每次使用前 手动启动服务   /etc/init.d/mysql start
2.开机自启   chkconfig mysql on     ,  chkconfig mysql off    
检查开机是否自动启动: ntsysv	
  使用空格取消选中, 然后按 TAB 确定!

2.3.4 Mysql 的重复启动问题

此时查看, 多了很多进程:

尝试去登录或者操作: 报错!
查看服务状态:
解决: 杀死所有和 mysql 进程相关的操作, 然后重启服务!
注意是 mysqld, d 代表 demon, 守护进程。
然后再重启:

2.4 修改字符集

2.4.1 常用命令

SQL 语句 描述 备注
show databases 列出所有数据库
create database 库名 创建一个数据库
create database 库名 character set utf8 创建数据库, 顺便执行字符集为 utf-8
show create database 库名 查看数据库的字符集
show variables like ‘%char%’ 查询所有跟字符集相关的信息
set [字符集属性]=utf8 设置相应的属性为 utf8 只是临时修改, 当前有效。 服务重启后, 失效。
alter database 库名 character set ‘utf8’ 修改数据库的字符集
alter table 表 名 convert to character set ‘utf8’ 修改表的字符集

实验 SQL:

CREATE database mydb;
CREATE table mytable(id int,name varchar(30));
insert into mytable(id,name) values (1,‘jack’);
insert into mytable(id,name) values (2,‘张三’)

2.4.2 如果在建库建表的时候, 没有明确指定字符集, 则采用默认的字符集 其中是不包含中文字符的。 查看默 认的编码字符集:

show variables like '%char%'

2.4.3 永久修改

(1) 修改配置文件
在/usr/share/mysql/ 中找到 my.cnf 的配置文件, 拷贝其中的 my-huge.cnf 到 /etc/ 并命名为 my.cnf 。 添加以下内容后再重启服务。
注意: 必须将文件拷贝到指定路径, 且名称为 my.cnf

[client]
default-character-set=utf8
[mysqld]
character_set_server=utf8
character_set_client=utf8
collation-server=utf8_general_ci
[mysql]
default-character-set=utf8

再次查看

注意: 已经创建的数据库的设定不会发生变化, 参数修改只对新建的数据库有效!
(2) 修改已创建库、 表字符集
修改数据库的字符集
mysql> alter database mydb character set ‘utf8’;
修改数据表的字符集
mysql> alter table mytbl convert to character set ‘utf8’;
(3) 修改已经乱码数据
无论是修改 mysql 配置文件或是修改库、 表字符集, 都无法改变已经变成乱码的数据。
只能删除数据重新插入或更新数据才可以完全解决

2.5 设置大小写不敏感

①查看大小写是否敏感: show variables like ‘%lower_case_table_names%’
windows 系统默认大小写不敏感, 但是 linux 系统是大小写敏感的

②设置大小写不敏感: 在 my.cnf 这个配置文件 [mysqld] 中加入 lower_case_table_names = 1 , 然后重启服务器

属性设置 描述
0 大小写敏感
1 大小写不敏感。 创建的表, 数据库都是以小写形式存放在磁盘上, 对于 sql 语句都是转换为 小写对表和 DB 进行查找
2 创建的表和 DB 依据语句上格式存放, 凡是查找都是转换为小写进行

注意: 如果要设置属性为大小写不敏感, 要在重启数据库实例之前就需要将原来的数据库和表转换为小写, 否则将
找不到数据库名。 在进行数据库参数设置之前, 需要掌握这个参数带来的影响, 切不可盲目设置。

2.6 sql_mode

sql_mode 定义了对 Mysql 中 sql 语句语法的校验规则!
sql_mode 是个很容易被忽视的变量, 默认值是空值, 在这种设置下是可以允许一些非法操作的, 比如允许一些
非法数据的插入。 在生产环境必须将这个值设置为严格模式, 所以开发、 测试环境的数据库也必须要设置, 这样在
开发测试阶段就可以发现问题。
2.6.1 sql_mode 常用的值

ONLY_FULL_GROUP_BY 对于 GROUP BY 聚合操作, 如果在 SELECT 中的列, 没有在 GROUP BY 中出现, 那么这个 SQL 是不合法的, 因为列不在 GROUP BY 从句中
NO_AUTO_VALUE_ON_ZERO 该值影响自增长列的插入。 默认设置下, 插入 0 或 NULL 代表生成下一个自增 长值。 如果用户 希望插入的值为 0, 而该列又是自增长的, 那么这个选项就有 用了
STRICT_TRANS_TABLES 在该模式下, 如果一个值不能插入到一个事务表中, 则中断当前的操作, 对非 事务表不做限制
NO_ZERO_IN_DATE 在严格模式下, 不允许日期和月份为零
NO_ZERO_DATE 设置该值, mysql 数据库不允许插入零日期, 插入零日期会抛出错误而不是警告
ERROR_FOR_DIVISION_BY_ZERO 在 INSERT 或 UPDATE 过程中, 如果数据被零除, 则产生错误而非警告。 如 果 未给出该模式, 那么数据被零除时 MySQL 返回 NULL
NO_AUTO_CREATE_USER 禁止 GRANT 创建密码为空的用户
NO_ENGINE_SUBSTITUTION 如果需要的存储引擎被禁用或未编译, 那么抛出错误。 不设置此值时, 用默认 的存储引擎替代, 并抛出一个异常
PIPES_AS_CONCAT 将"||"视为字符串的连接操作符而非或运算符, 这和 Oracle 数据库是一样的, 也和字符串的拼接函数 Concat 相类似
ANSI_QUOTES 启用 ANSI_QUOTES 后, 不能用双引号来引用字符串, 因为它被解释为识别符

2.6.2 查看和修改
①查看当前的 sql_mode: select @@sql_mode;
②sql_mode 的影响案例: group by 查询语法错误!

CREATE TABLE mytbl2 (id INT,NAME VARCHAR(200),age INT,dept INT);
INSERT INTO mytbl2 VALUES(1,'zhang3',33,101);
INSERT INTO mytbl2 VALUES(2,'li4',34,101);
INSERT INTO mytbl2 VALUES(3,'wang5',34,102);
INSERT INTO mytbl2 VALUES(4,'zhao6',34,102);
INSERT INTO mytbl2 VALUES(5,'tian7',36,102);

查询每个 dept 中年龄最大的人:SELECT NAME,dept,MAX(age) FROM mytbl2 GROUP BY dept;

正 确 写 法 : SELECT id,name,ab.dept,ab.maxage FROM mytbl2 m INNER JOIN(SELECT dept,MAX(age)maxage FROM mytbl2
GROUP BY dept)ab ON ab.dept=m.dept AND m.age=ab.maxage;

③临时修改 sql_mode: set @@sql_mode=’’;
④永久修改, 需要在配置文件 my.cnf 中修改:
[mysqld] 下添加 sql_mode=’’ 然后重启 mysql 即可

第 2 章 MySql 的用户和权限管理

1. Mysql 的用户管理

1.1 相关命令

命令 描述 备注
create user zhang3 identified by ‘123123’; 创建名称为 zhang3 的用户, 密码设为 123123;
select host,user,password,select_priv,insert_priv,drop_priv from mysql.user; 查看用户和权限的相关信 息
set password =password(‘123456’) 修改当前用户的密码
update mysql.user set password=password(‘123456’) where user=‘li4’; 修改其他用户的密码 所有通过 user 表的修改, 必须 用 flush privileges;命令才能生 效
update mysql.user set user=‘li4’ where user=‘wang5’; 修改用户名 所有通过 user 表的修改, 必须 用 flush privileges;命令才能生 效
drop user li4 删除用户 不要通过 delete from user u where user=‘li4’ 进行删除, 系 统会有残留信息保留。

1.2 示例说明

host :表示连接类型
% 表示所有远程通过 TCP 方式的连接
IP 地址 如 (192.168.1.2,127.0.0.1) 通过制定 ip 地址进行的 TCP 方式的连接
机器名 通过制定 i 网络中的机器名进行的 TCP 方式的连接
::1 IPv6 的本地 ip 地址 等同于 IPv4 的 127.0.0.1
localhost 本地方式通过命令行方式的连接 , 比如 mysql -u xxx -p 123xxx 方式的连接。
user:表示用户名
同一用户通过不同方式链接的权限是不一样的。
password:密码
所有密码串通过 password(明文字符串) 生成的密文字符串。 加密算法为 mysqlSHA1 , 不可逆 。
mysql 5.7 的密码保存到 authentication_string 字段中不再使用 password 字段。
select_priv , insert_priv 等为该用户所拥有的权限。

2.Mysql 的权限管理

2.1 授予权限

命令 描述
grant 权限 1,权限 2,…权限 n on 数据库名称. 表名称 to 用户名@用户地址 identified by ‘连接口令’ 该权限如果发现没有该用户, 则会直接新建一个用户。 示例: grant select,insert,delete,drop on atguigudb.* to li4@localhost ; 给 li4 用户用本地命令行方式下, 授予 atguigudb 这个库下的所有 表的插删改查的权限。
grant all privileges on . to joe@’%’ identified by ‘123’; 授予通过网络方式登录的的 joe 用户 , 对所有库所有表的全部权 限, 密码设为 123

2.2 收回权限

命令 描述 备注
show grants 查看当前用户权限
revoke [权限 1,权限 2,…权限 n] on 库名.表名 from 用户名@用户地址 ; 收回权限命令
REVOKE ALL PRIVILEGES ON mysql.* FROM joe@localhost; 收回全库全表的所有权限
REVOKE select,insert,update,delete ON mysql.* FROM joe@localhost; 收回 mysql 库下的所有表的插删改查 权限

权限收回后, 必须用户重新登录后, 才能生效

2.3 查看权限

命令 描述 备注
show grant; 查看当前用户权限
select * from user;

第 3 章 Mysql 逻辑架构简介

1. 整体架构图

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

各层介绍:

1.1 连接层

最上层是一些客户端和连接服务, 包含本地 sock 通信和大多数基于客户端/服务端工具实现的类似于 tcp/ip 的通信。 主要完成一些类似于连接处理、 授权认证、 及相关的安全方案。 在该层上引入了线程池的概念, 为通过认证安全接入的客户端提供线程。 同样在该层上可以实现基于 SSL 的安全链接。 服务器也会为安全接入的每个客户端验证它所具有的操作权限

1.2 服务层

Management Serveices & Utilities 系统管理和控制工具
SQL Interface: SQL 接口。 接受用户的 SQL 命令, 并且返回用户需要查询的结果。 比如 select from 就是调用 SQL Interface
Parser 解析器。 SQL 命令传递到解析器的时候会被解析器验证和解析
Optimizer 查询优化器。 SQL 语句在查询之前会使用查询优化器对查询进行优化, 比如有 where 条件时, 优化器来决定先投影还是先过滤。
Cache 和 Buffer 查询缓存。 如果查询缓存有命中的查询结果, 查询语句就可以直接去查询缓存中取 数据。 这个缓存机制是由一系列小缓存组成的。 比如表缓存, 记录缓存, key 缓存, 权限缓存等

1.3.引擎层

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

1.4.存储层

数据存储层, 主要是将数据存储在运行于裸设备的文件系统之上, 并完成与存储引擎的交互。

2. show profile

利用 show profile 可以查看 sql 的执行周期!

2.1 开启 profile

查看 profile 是否开启: show variables like ‘%profiling%’

如果没有开启, 可以执行 set profiling=1 开启!

2.2 使用 profile

执行 show prifiles 命令, 可以查看最近的几次查询

根据 Query_ID,可以进一步执行 show profile cpu,block io for query Query_id 来查看 sql 的具体执行步骤。

2.3 大致的查询流程

mysql 的查询流程大致是:
mysql 客户端通过协议与 mysql 服务器建连接, 发送查询语句, 先检查查询缓存, 如果命中, 直接返回结果,否则进行语句解析,也就是说, 在解析查询之前, 服务器会先访问查询缓存(query cache)——它存储 SELECT 语句以及相应的查询结果集。 如果某个查询结果已经位于缓存中, 服务器就不会再对查询进行解析、 优化、 以及执行。 它仅仅将缓存中的结果返回给用户即可, 这将大大提高系统的性能。
语法解析器和预处理: 首先 mysql 通过关键字将 SQL 语句进行解析, 并生成一颗对应的“解析树”。 mysql 解析器将使用 mysql 语法规则验证和解析查询; 预处理器则根据一些 mysql 规则进一步检查解析数是否合法。
查询优化器当解析树被认为是合法的了, 并且由优化器将其转化成执行计划。 一条查询可以有很多种执行方式,最后都返回相同的结果。 优化器的作用就是找到这其中最好的执行计划。
然后, mysql 默认使用的 BTREE 索引, 并且一个大致方向是:无论怎么折腾 sql, 至少在目前来说, mysql 最多只用到表中的一个索引

2.4 SQL 的执行顺序

随着 Mysql 版本的更新换代, 其优化器也在不断的升级, 优化器会分析不同执行顺序产生的性能消耗不同而动
态调整执行顺序。 下面是经常出现的查询顺序:

手写的顺序

select distinct
from
on
where
group by
having
order by
limit

真正执行的顺序:

from
on
join
where
group by
having
select
distinct
order by
limit

2.5 MyISAM 和 InnoDB

对比项 MyISAM InnoDB
外键 不支持 支持
事务 不支持 支持
行表锁 表锁, 即使操作一条记录也会锁住整个表, 不适合高并发的操作 行锁,操作时只锁某一行, 不对其它行有影响, 适合高并发的操作
缓存 只缓存索引, 不缓存真实数据 不仅缓存索引还要缓存真实数据, 对内存要求较高, 而且内 存大小对性能有决定性的影响
show engines:查看所有的数据库引擎
  
show variables like '%storage_engine%' 查看默认的数据库引擎  

局部性原理:查一行数据的时候,会先从磁盘放到内存,然后从内存中取取来进行操作。而然因为局部性原理,查出了这一行代表有很大几率他的附近的行也会被将来使用到,所以不仅会查出这一行,还会查出这一行前后的一些行。

取的时候通常是取一页,那么什么是页呢?

一页=4KB,在mysql中页的单位是16KB。

我们查寻的出来的一行

COMPACT行格式:变长字段长度列表,变长字段所对应的长度。不需要以key,value格式存储,因为字段顺序是固定的,所以只存储值就可以。

除了BLOB以外的字段总共不能超过65535字节,其中变长字段长度+NULL标志位还会占用2+1个字节,所以字段最大65532字节。此时一行数据一页都容不下,所以得分为几页,这叫做行溢出。另外页之间还需要存下一页的地址。

如果有有主键,插入的顺序跟实际的顺序是不一样的。如果没有主键,插入的熟往往就是实际存储的顺序。聚集于堆表。

将每行数据按组划分,这样我们只需要存储每组的地址作为目录也能加快查找。

还可以把目录优化为树。此时每页里会维护一个目录,而还有有另外一个总某一列维护整张table的目录。前者叫页目录,后者叫目录页。目录页存储以下内容:key、value分别存储序号与地址。

先看有没有主键,再看有没有唯一索引,然后才自动生成rowID。主键使用自增id才会连续存储,不会出现页中查入整体往后移动的情况。

如果目标页超过了,那么还需要分,这样就会增高树的高度。

如果在第一页前面插入,那么第一页复制到第二页,再把之前的第一页改为目录页。

第 4 章 SQL 预热

1. 常见的 Join 查询图

2. Join 示例

2.1 建表语句

员工表与部门表。

CREATE TABLE `t_dept` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `deptName` VARCHAR(30) DEFAULT NULL,
    `address` VARCHAR(40) DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE `t_emp` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(20) DEFAULT NULL,
    `age` INT(3) DEFAULT NULL,
    `deptId` INT(11) DEFAULT NULL,
    empno int not null,
    PRIMARY KEY (`id`),
    KEY `idx_dept_id` (`deptId`)
    #CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `t_dept` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

INSERT INTO t_dept(deptName,address) VALUES('华山','华山');
INSERT INTO t_dept(deptName,address) VALUES('丐帮','洛阳');
INSERT INTO t_dept(deptName,address) VALUES('峨眉','峨眉山');
INSERT INTO t_dept(deptName,address) VALUES('武当','武当山');
INSERT INTO t_dept(deptName,address) VALUES('明教','光明顶');
INSERT INTO t_dept(deptName,address) VALUES('少林','少林寺');
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('风清扬',90,1,100001);  
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('岳不群',50,1,100002);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('令狐冲',24,1,100003);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('洪七公',70,2,100004);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('乔峰',35,2,100005);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('灭绝师太',70,3,100006);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('周芷若',20,3,100007);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('张三丰',100,4,100008);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('张无忌',25,5,100009);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('韦小宝',18,null,100010);  

2.2 案例

1.所有有门派人员的信息(要求显示门派名称)
SELECT e.name,d.deptName FROM t_emp e INNER JOIN t_dept d ON e.deptId=d.id;
2. 列出所有人员及其门派信息
SELECT e.name,d.deptName FROM t_emp e LEFT JOIN t_dept d ON e.deptId=d.id;
3. 列出所有门派
SELECT * FROM t_dept;
4. 所有无门派人士
SELECT * FROM t_emp WHERE deptId IS NULL;
5. 所有无人门派
SELECT d.* FROM t_dept d LEFT JOIN t_emp e ON d.id=e.deptId WHERE e.deptId IS NULL;
6. 所有人员和门派的对应关系
SELECT * FROM t_emp e LEFT JOIN t_dept d ON e.deptId=d.id UNION SELECT * FROM t_emp e RIGHT JOIN t_dept d ON e.deptId=d.id;
7. 所有没有入门派的人员和没人入的门派
SELECT * FROM t_emp e LEFT JOIN t_dept d ON e.deptId=d.id WHERE e.deptId IS NULL UNION SELECT * FROM t_dept d LEFT JOIN t_emp e ON d.id=e.deptId WHERE e.deptId IS NULL;
8. 添加 CEO 字段 ALTER TABLE t_dept add CEO INT(11) ; update t_dept set CEO=2 where id=1; update t_dept set CEO=4 where id=2; update t_dept set CEO=6 where id=3; update t_dept set CEO=8 where id=4; update t_dept set CEO=9 where id=5;
8.1 求各个门派对应的掌门人名称
SELECT d.deptName,e.name FROM t_dept d LEFT JOIN t_emp e ON d.ceo=e.id
8.2 求所有当上掌门人的平均年龄
SELECT AVG(e.age) FROM t_dept d LEFT JOIN t_emp e ON d.ceo=e.id
8.3 求所有人物对应的掌门名称
SELECT ed.name ‘人物’,c.name ‘掌门’ FROM (SELECT e.name,d.ceo from t_emp e LEFT JOIN t_dept d on e.deptid=d.id) ed LEFT JOIN t_emp c on ed.ceo= c.id;
SELECT e.name ‘人物’,tmp.name ‘掌门’ FROM t_emp e LEFT JOIN (SELECT d.id did,e.name FROM t_dept d LEFT JOIN t_emp e ON d.ceo=e.id)tmp ON e.deptId=tmp.did;
SELECT e1.name ‘人物’,e2.name ‘掌门’ FROM t_emp e1 LEFT JOIN t_dept d on e1.deptid = d.id LEFT JOIN t_emp e2 on d.ceo = e2.id ;
SELECT e2.name ‘人物’,
(SELECT e1.name FROM t_emp e1 where e1.id= d.ceo) '掌门’
from t_emp e2 LEFT JOIN t_dept d on e2.deptid=d.id;
show create table test;
show index from test;
alter table test add constraint indexid primary key(id);
alter table test add constraint PC unique index(tcid)

第 5 章 索引优化分析

1. 索引的概念

1.1 是什么

MySQL 官方对索引的定义为: 索引(Index) 是帮助 MySQL 高效获取数据的数据结构。 可以得到索引的本质:
索引是数据结构。可以简单理解为排好序的快速查找数据结构。
在数据之外, 数据库系统还维护着满足特定查找算法的数据结构, 这些数据结构以某种方式引用(指向) 数据,这样就可以在这些数据结构上实现高级查找算法。 这种数据结构, 就是索引。

左边是数据表, 一共有两列七条记录, 最左边的是数据记录的物理地址。 为了加快 Col2 的查找, 可以维护一个右边所示的二叉查找树, 每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针, 这样就可以运用二叉查找在一定的复杂度内获取到相应数据, 从而快速的检索出符合条件的记录。

一般来说索引本身也很大, 不可能全部存储在内存中, 因此索引往往以索引文件的形式存储的磁盘上

索引对多个值进行排序的依据是CREATE TABLE语句中定义索引时列的顺序

下图就是一种可能的索引方式示例:

1.2 优缺点

优势:

  • 提高数据检索的效率, 降低数据库的IO成本。
  • 通过索引列对数据进行排序, 降低数据排序的成本, 降低了CPU的消耗。

劣势:

  • 虽然索引大大提高了查询速度, 同时却会降低更新表的速度, 如对表进行INSERT、 UPDATE和DELETE。 因为更新表时, MySQL不仅要保存数据, 还要保存一下索引文件每次更新添加了索引列的字段, 都会调整因为更新所带来的键值变化后的索引信息。
  • 实际上索引也是一张表, 该表保存了主键与索引字段, 并指向实体表的记录, 所以索引列也是要占用空间
    的。

索引分类

  • 顺序文件索引:文件按搜索码的顺序存储,索引记录按搜索码值排序–主索引。顺序索引可以是稀疏索引(隔几行建立一个索引)和稠密索引(说个搜索码值都建立一个索引)。
    • 稠密索引只要有一个记录删除,就整个索引文件就需要进行维护
  • B+树索引:B+树索引可以作为主索引,也可以作为辅助索引。可以是稠密的,也可以是稀疏的。
    • 支持范围查询,支持随机查询

2. Mysql 的索引

索引在 MySQL 数据库中分三类:

  • B+ 树索引
  • Hash 索引
  • 全文索引

参考 https://www.cnblogs.com/zhuyeshen/p/12082839.html

2.1 Btree 索引

MySQL 使用的是 B-tree 索引(有没有-都是一个词)。

  • 1)树中每个结点至多有m 棵子树(注:m指的是树的阶);
  • 2)根节点的儿子数为:[2,M];(除非根节点为唯一结点)
  • 3)除根结点之外的所有非叶子结点子树:[ceil(m/2),m] ,(ceil为向上取整。);
  • 4)所有的非叶子结点中包含以下数据:(n,A0,K1,A1,K2,…,Kn,An)
  • 所有叶子结点在同一层
  • 图中的每个节点称为页,页就是我们上面说的磁盘块,在 MySQL 中数据读取的基本单位都是页,所以我们这里叫做页更符合 MySQL 中索引的底层数据结构。

img

【初始化介绍】

一颗 b 树, 浅蓝色的块我们称之为一个磁盘块, 可以看到每个磁盘块包含几个数据项(深蓝色所示) 和指针(黄色所示), 如磁盘块 1 包含数据项 17 和 35, 包含指针 P1、 P2、 P3,

P1 表示小于 17 的磁盘块, P2 表示在 17 和 35 之间的磁盘块, P3 表示大于 35 的磁盘块。

真实的数据存在于叶子节点即 3、 5、 9、 10、 13、 15、 28、 29、 36、 60、 75、 79、 90、 99。

非叶子节点只不存储真实的数据, 只存储指引搜索方向的数据项, 如 17、 35 并不真实存在于数据表中。

【查找过程】

如果要查找数据项 29, 那么首先会把磁盘块 1 由磁盘加载到内存, 此时发生一次 IO, 在内存中用二分查找确定 29在 17 和 35 之间, 锁定磁盘块 1 的 P2 指针, 内存时间因为非常短(相比磁盘的 IO) 可以忽略不计, 通过磁盘块 1的 P2 指针的磁盘地址把磁盘块 3 由磁盘加载到内存, 发生第二次 IO, 29 在 26 和 30 之间, 锁定磁盘块 3 的 P2 指针,通过指针加载磁盘块 8 到内存, 发生第三次 IO, 同时内存中做二分查找找到 29, 结束查询, 总计三次 IO。

真实的情况是, 3 层的 b+树可以表示上百万的数据, 如果上百万的数据查找只需要三次 IO, 性能提高将是巨大的,

如果没有索引, 每个数据项都要发生一次 IO, 那么总共需要百万次的 IO, 显然成本非常非常高。

2.2 B+tree 索引

B+树只有达到叶子结点才命中(B树可以在非叶子结点命中)

img

B+树 只有叶子节点存储data,叶子节点包含了这棵树的所有键值,叶子节点不存储指针。

后来,在B+树上增加了顺序访问指针,也就是每个叶子节点增加一个指向相邻叶子节点的指针,这样一棵树成了数据库系统实现索引的首选数据结构。

B+Tree 与 B-Tree 的区别
  • B-树的关键字和记录是放在一起的, 叶子节点可以看作外部节点, 不包含任何信息; B+树的非叶子节点中只有关键字和指向下一个节点的索引, 记录只放在叶子节点中。

  • B+ 树非叶子节点上是不存储数据的,仅存储键值,而 B 树节点中不仅存储键值,也会存储数据。

    之所以这么做是因为在数据库中页的大小是固定的,InnoDB 中页的默认大小是 16KB。

    如果不存储数据,那么就会存储更多的键值,相应的树的阶数(节点的子节点树)就会更大,树就会更矮更胖,如此一来我们查找数据进行磁盘的 IO 次数又会再次减少,数据查询的效率也会更快。

    另外,B+ 树的阶数是等于键值的数量的,如果我们的 B+ 树一个节点可以存储 1000 个键值,那么 3 层 B+ 树可以存储 1000×1000×1000=10 亿个数据。

    因为 B+树的非叶子节点不存放实际的数据,这样每个节点可容纳的元素个数比 B-树多树高比 B-树小

    这样带来的好处是减少磁盘访问次数(费时)。 尽管 B+树找到一个记录所需的比较次数要比 B-树多, 但是一次磁盘访问的时间相当于成百上千次内存比较的时间, 因此实际中B+树的性能可能还会好些, 而且 B+树的叶子节点使用指针连接在一起, 方便顺序遍历(例如查看一个目录下的所有文件, 一个表中的所有记录等), 这也是很多数据库和文件系统使用 B+树的缘故。

    一般根节点是常驻内存的,所以一般我们查找 10 亿数据,只需要 2 次磁盘 IO。

  • 因为 B+ 树索引的所有数据均存储在叶子节点,而且数据是按照顺序排列的。

    那么 B+ 树使得范围查找,排序查找,分组查找以及去重查找变得异常简单。而 B 树因为数据分散在各个节点,要实现这一点是很不容易的。

    有心的读者可能还发现上图 B+ 树中各个页之间是通过双向链表连接的,叶子节点中的数据是通过单向链表连接的。

    其实上面的 B 树我们也可以对各个节点加上链表。这些不是它们之前的区别,是因为在 MySQL 的 InnoDB 存储引擎中,索引就是这样存储的。

    也就是说上图中的 B+ 树索引就是 InnoDB 中 B+ 树索引真正的实现方式,准确的说应该是聚集索引(聚集索引和非聚集索引下面会讲到)。

    通过上图可以看到,在 InnoDB 中,我们通过数据页之间通过双向链表连接以及叶子节点中数据之间通过单向链表连接的方式可以找到表中所有的数据。

    MyISAM 中的 B+ 树索引实现与 InnoDB 中的略有不同。在 MyISAM 中,B+ 树索引的叶子节点并不存储数据,而是存储数据的文件地址。

  • 在 B-树中, 越靠近根节点的记录查找时间越快, 只要找到关键字即可确定记录的存在; 而 B+树中每个记录的查找时间基本是一样的, 都需要从根节点走到叶子节点, 而且在叶子节点中还要再比较关键字。 从这个角度看 B-树的性能好像要比 B+树好, 而在实际应用中却是 B+树的性能要好些。

一般来说B+Tree比BTree更适合实现外存的索引结构,因为存储引擎的设计专家巧妙的利用了外存(磁盘)的存储结构,即磁盘的一个扇区是整数倍的page(页),页是存储中的一个单位,通常默认为4K,因此索引结构的节点被设计为一个页的大小,然后利用外存的“预读取”原则,每次读取的时候,把整个节点的数据读取到内存中,然后在内存中查找,已知内存的读取速度是外存读取I/O速度的几百倍,那么提升查找速度的关键就在于尽可能少的磁盘I/O,那么可以知道,每个节点中的key个数越多,那么树的高度越小,需要I/O的次数越少,因此一般来说B+Tree比BTree更快,因为B+Tree的非叶节点中不存储data,就可以存储更多的key。

思考: 为什么说 B+树比 B-树更适合实际应用中操作系统的文件索引和数据库索引?

  1. B+树的磁盘读写代价更低

B+树的内部结点并没有指向关键字具体信息的指针。 因此其内部结点相对 B 树更小。 如果把所有同一内部结点的关键字存放在同一盘块中, 那么盘块所能容纳的关键字数量也越多。 一次性读入内存中的需要查找的关键字也就越多。 相对来说 IO 读写次数也就降低了。

  1. B+树的查询效率更加稳定

由于非终结点并不是最终指向文件内容的结点, 而只是叶子结点中关键字的索引。 所以任何关键字的查找必须走一条从根结点到叶子结点的路。 所有关键字查询的路径长度相同, 导致每一个数据的查询效率相当。

MyISAM

data存的是数据地址。索引是索引,数据是数据。

img

InnoDB

data存的是数据本身。索引也是数据。

img

1、MyISAM是非事务安全的,而InnoDB是事务安全的

2、MyISAM锁的粒度是表级的,而InnoDB支持行级锁

3、MyISAM支持全文类型索引,而InnoDB不支持全文索引

4、MyISAM相对简单,效率上要优于InnoDB,小型应用可以考虑使用MyISAM

5、MyISAM表保存成文件形式,跨平台使用更加方便

6、MyISAM管理非事务表,提供高速存储和检索以及全文搜索能力,如果在应用中执行大量select操作可选择

7、InnoDB用于事务处理,具有ACID事务支持等特性,如果在应用中执行大量insert和update操作,可选择。

2.3 聚簇索引和非聚簇索引

聚集索引(聚簇索引):以 InnoDB 作为存储引擎的表,表中的数据都会有一个主键,即使你不创建主键,系统也会帮你创建一个隐式的主键。

这是因为 InnoDB 是把数据存放在 B+ 树中的,而 B+ 树的键值就是主键,在 B+ 树的叶子节点中,存储了表中所有的数据。

这种以主键作为 B+ 树索引的键值而构建的 B+ 树索引,我们称之为聚集索引。

非聚集索引(非聚簇索引):以主键以外的列值作为键值构建的 B+ 树索引,我们称之为非聚集索引。

非聚集索引与聚集索引的区别在于非聚集索引的叶子节点不存储表中的数据,而是存储该列对应的主键,想要查找数据我们还需要根据主键再去聚集索引中进行查找,这个再根据聚集索引查找数据的过程,我们称为回表。

明白了聚集索引和非聚集索引的定义,我们应该明白这样一句话:数据即索引,索引即数据。

聚簇索引并不是一种单独的索引类型, 而是一种数据存储方式。 术语‘聚簇’ 表示数据行和相邻的键值聚簇的存储在一起。 如下图, 左侧的索引就是聚簇索引, 因为数据行在磁盘的排列和索引排序保持一致。

数据库中相邻的数据在索引中也相邻就是聚簇索引

利用聚集索引和非聚集索引查找数据

前面我们讲解 B+ 树索引的时候并没有去说怎么在 B+ 树中进行数据的查找,主要就是因为还没有引出聚集索引和非聚集索引的概念。

下面我们通过讲解如何通过聚集索引以及非聚集索引查找数据表中数据的方式介绍一下 B+ 树索引查找数据方法。

利用聚集索引查找数据

7

还是这张 B+ 树索引图,现在我们应该知道这就是聚集索引,表中的数据存储在其中。

现在假设我们要查找 id>=18 并且 id<40 的用户数据。对应的 sql 语句为:

select * from user where id>=18 and id <40

其中 id 为主键,具体的查找过程如下:

①一般根节点都是常驻内存的,也就是说页 1 已经在内存中了,此时不需要到磁盘中读取数据,直接从内存中读取即可。

从内存中读取到页 1,要查找这个 id>=18 and id <40 或者范围值,我们首先需要找到 id=18 的键值。

从页 1 中我们可以找到键值 18,此时我们需要根据指针 p2,定位到页 3。

②要从页 3 中查找数据,我们就需要拿着 p2 指针去磁盘中进行读取页 3。

从磁盘中读取页 3 后将页 3 放入内存中,然后进行查找,我们可以找到键值 18,然后再拿到页 3 中的指针 p1,定位到页 8。

③同样的页 8 页不在内存中,我们需要再去磁盘中将页 8 读取到内存中。

将页 8 读取到内存中后。因为页中的数据是链表进行连接的,而且键值是按照顺序存放的,此时可以根据二分查找法定位到键值 18。

此时因为已经到数据页了,此时我们已经找到一条满足条件的数据了,就是键值 18 对应的数据。

因为是范围查找,而且此时所有的数据又都存在叶子节点,并且是有序排列的,那么我们就可以对页 8 中的键值依次进行遍历查找并匹配满足条件的数据。

我们可以一直找到键值为 22 的数据,然后页 8 中就没有数据了,此时我们需要拿着页 8 中的 p 指针去读取页 9 中的数据。

④因为页 9 不在内存中,就又会加载页 9 到内存中,并通过和页 8 中一样的方式进行数据的查找,直到将页 12 加载到内存中,发现 41 大于 40,此时不满足条件。那么查找到此终止。

最终我们找到满足条件的所有数据,总共 12 条记录:

(18,kl), (19,kl), (22,hj), (24,io), (25,vg) , (29,jk), (31,jk) , (33,rt) , (34,ty) , (35,yu) , (37,rt) , (39,rt) 。

下面看下具体的查找流程图

8

利用非聚集索引查找数据

9

读者看到这张图的时候可能会蒙,这是啥东西啊?怎么都是数字。如果有这种感觉,请仔细看下图中红字的解释。

什么?还看不懂?那我再来解释下吧。首先,这个非聚集索引表示的是用户幸运数字的索引(为什么是幸运数字?一时兴起想起来的:-)),此时表结构是这样的。

img

在叶子节点中,不再存储所有的数据了,存储的是键值和主键。对于叶子节点中的 x-y,比如 1-1。左边的 1 表示的是索引的键值,右边的 1 表示的是主键值。

如果我们要找到幸运数字为 33 的用户信息,对应的 sql 语句为:

select * from user where luckNum=33

查找的流程跟聚集索引一样,这里就不详细介绍了。我们最终会找到主键值 47,找到主键后我们需要再到聚集索引中查找具体对应的数据信息,此时又回到了聚集索引的查找流程。

下面看下具体的查找流程图:[

](http://www.liuzk.com/wp-content/uploads/2019/11/92.jpg)

92[

](http://www.liuzk.com/wp-content/uploads/2019/11/92.jpg)

在 MyISAM 中,聚集索引和非聚集索引的叶子节点都会存储数据的文件地址。

总结

本篇文章从二叉查找树,详细说明了为什么 MySQL 用 B+ 树作为数据的索引,以及在 InnoDB 中数据库如何通过 B+ 树索引来存储数据以及查找数据。我们一定要记住这句话:数据即索引,索引即数据。

聚簇索引的好处:

  • 按照聚簇索引排列顺序, 查询显示一定范围数据的时候, 由于数据都是紧密相连, 数据库不不用从多个数据块中提取数据, 所以节省了大量的 io 操作。

如果没有定义主键,InnoDB会选择一个唯一的非空索引代替。如果没有这样的索引,InnoDB会隐式地定义一个主键来作为聚簇索引。InnoDB只聚集在同一个页面中的记录。包含相邻键值的页面可能会相距甚远。

聚簇主键可能对性能有帮助,但也可能导致严重的性能问题。所以需要仔细地考虑聚簇索引,由其将表的存储索引从InnoDB改为其他索引的时候(反过来也一样)

聚簇索引的限制:

对于 mysql 数据库目前只有 innodb 数据引擎支持聚簇索引, 而 Myisam 并不支持聚簇索引

由于数据物理存储排序方式只能有一种, 所以每个 Mysql 的表只能有一个聚簇索引。 一般情况下就是该表的主键。
为了充分利用聚簇索引的聚簇的特性, 所以 innodb 表的主键列尽量选用有序的顺序 id, 而不建议用
无序的 id, 比如 uuid 这种。

2.4 时间复杂度(扩展)

同一问题可用不同算法解决, 而一个算法的质量优劣将影响到算法乃至程序的效率。 算法分析的目的在于选择合适算法和改进算法。

时间复杂度是指执行算法所需要的计算工作量, 用大 O 表示记为: O(…)

3. Mysql 索引分类

3.1 单值索引

概念: 即一个索引只包含单个列, 一个表可以有多个单列索引
语法:

CREATE TABLE customer (
    id INT(10) UNSIGNED AUTO_INCREMENT ,
    customer_no VARCHAR(200),
    customer_name VARCHAR(200),
    PRIMARY KEY(id),-- 后加主键
    KEY (customer_name)
);
或者
CREATE INDEX idx_customer_name ON customer(customer_name);
或者
alter table 表名 add index 索引名(字段) ;

3.2 唯一索引

概念: 索引列的值必须唯一, 但允许有空值

CREATE TABLE customer (
    id INT(10) UNSIGNED AUTO_INCREMENT ,
    customer_no VARCHAR(200),
    customer_name VARCHAR(200),
    PRIMARY KEY(id),
    KEY (customer_name),
    UNIQUE (customer_no)
);
或者
CREATE UNIQUE INDEX idx_customer_no ON customer(customer_no);
或者
alter table tb add unique index name_index(name);

3.3 主键索引

概念: 设定为主键后数据库会自动建立索引, 不能为null,innodb为聚簇索引

CREATE TABLE customer (
    id INT(10) UNSIGNED AUTO_INCREMENT ,
    customer_no VARCHAR(200),
    customer_name VARCHAR(200),
    PRIMARY KEY(id)
);
或者
ALTER TABLE customer add PRIMARY KEY customer(customer_no);
删除建主键索引:
ALTER TABLE customer drop PRIMARY KEY ;
修改建主键索引:
必须先删除掉(drop)原索引, 再新建(add)索引

3.4 复合索引

概念: 即一个索引包含多个列

CREATE TABLE customer (
    id INT(10) UNSIGNED AUTO_INCREMENT ,
    customer_no VARCHAR(200),
    customer_name VARCHAR(200),
    PRIMARY KEY(id),
    KEY (customer_name),
    UNIQUE (customer_name),
    KEY (customer_no,customer_name) --  复合索引
);
或者
CREATE INDEX idx_no_name ON customer(customer_no,customer_name);
或者
alter table tb add index dept_name_index(dept,name);

3.5 基本语法

操作 命令
创建 CREATE [UNIQUE ] INDEX [indexName] ON table_name(column))
删除 DROP INDEX [indexName] ON mytable;
查看 SHOW INDEX FROM table_name\G
使用Alter ALTER TABLE tbl_name ADD PRIMARY KEY (column_list) : 该语句添加一个主键, 这意味着索引值必须是唯一 的, 且不能为 NULL。
ALTER TABLE tbl_name ADD INDEX index_name (column_list): 添加普通索引, 索引值可出现多次。
ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list):该语句指定了索引为 FULLTEXT , 用于全文索 引。

4. 索引的创建时机

4.1 适合创建索引的情况

  • 主键自动建立唯一索引;
  • 频繁作为查询条件的字段应该创建索引
  • 查询中与其它表关联的字段, 外键关系建立索引
  • 单键/组 合索引的选择问题, 组合索引性价比更高
  • 查询中排序的字段, 排序字段若通过索引去访问将大大提高排序速度
  • 查询中统计或者分组字段

4.2 不适合创建索引的情况

  • 表记录太少
  • 经常增删改的表或者字段
  • Where 条件里用不到的字段不创建索引
  • 过滤性不好的不适合建索引

5 索引对如下类型的查询有效

比如我们有一个符合索引:lastName,firstName,birth。注:lastName为中文的姓,firstName为名

  • 全值匹配:指的是和索引中很多所有列进行匹配,例如查找Cuba Allen,birth="1993-01-01"的人
  • 匹配最左前缀:查找所有lastName="Allen"的人,即值使用索引的第一列
  • 匹配列前缀:只匹配某一列的值的开头部分。李立儒查找J开头的姓的人,这里只使用了索引的第一列
  • 匹配范围值:例如查找姓在"Allen"和"Li"之间的人,只使用了索引的第一列
  • 精确匹配某一列并范围匹配另一列:查找姓为"Allen",名开头为"K"的人。即第一列lastName全匹配,第二列firstName范围匹配
  • 只访问索引的查询:B树通常可以支持“只访问索引的查询”,即查询只需要访问索引,而无需访问数据行。(这被称为索引覆盖)

因为索引树中的结点是有序的,所以除了可以按值查找之外,索引还可以用于查询中的ORDER BY操作(按顺序操作)。一般来说,如果B树可以按照某种方式查找到值,那么也可以按照这种方式用于排序。所以,如果ORDER BY子句满足前面列出的几种查询类型,则这个索引页可以满足对应的排序要求。

下面是一些关于B树索引的限制:

  • 如果不是按照索引的最左列开始查找,则无法使用索引。例如上面的索引直接查birth=“2000-01-01"的人,因为不是最左列。同理也不能查找 姓式以某个字母结尾的人,如”*llen"
  • 不能跳过索引中的列。例如不能查找姓为"Allen",birth="2020-01-01"的人,因为跳过了名"Cuba "
  • 如果查询中有某个列的范围查询,则右面所有列都无法使用索引进行优化查找。如where lastName='Allen' and firstName like "C*" and birth="1993-01-01",这个索引只能使用索引的前两列,因为like是一个范围查询。

6 哈希索引

哈希索引hash index基于哈希表实现,只有精确匹配索引所有列的查询才有效。对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码hash code,哈希码是一个较小的值,并且不同的键值行计算出来的哈希码也不一样。哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指每个数据行的指针。

例如

CREATE TABLE testhash(
    fname VARCHAR(50) NOT NULL,
    lname VARCHAR(50) NOT NULL,
    KEY USING HASH(fname)
)ENGINE=MEMORY;

哈希表中哈希码为槽,哈希表中值为对应的指针

注意每个槽的编号是顺序的,但是数据航不是。现在,来看如下查询:

select lname from testhash where fname='Peter';

MySQL先计算 ‘Peter’的哈希值并使用该值寻找对应的记录指针。因为f( Peter")=8784,所以 MySQL在索引中查找8784,可以找到指向第3行的指针,最后一步是比较第三行的值是否为 Peter’,以确保就是要查找的行。

因为索引自身只需存储对应的哈希值,所以索引的结构十分紧凑,这也让哈希索引查找的速度非常快。然而,哈希索引也有它的限制:

  • 哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。不过,访问内存中的行的速度很快,所以大部分情况下这一点对性能的影响并不明显。
  • 哈希索引数据并不是按照索引值顺序存储的,所以也就无法用于排序。
  • 哈希索引也不支持部分索引列匹配査找,因为哈希索引始终是使用索引列的全部内容来计算哈希值的。例如,在数据列(A,B)上建立哈希索引,如果查询只有数据列A,则无法使用该索引
  • 哈希索引只支持等值比较查询,包括=、IN()、<(注意心和<=>是不同的操作)。也不支持任何范围查询,例如 WHERE price>10
  • 访问哈希索引的数据非常快,除非有很多哈希冲突(不同的索引列值却有相同的哈希值)。当出现哈希冲突的时候,存储引擎必须遍历链表中所有的行指针,逐行进行比较,直到找到所有符合条件的行。
  • 如果哈希冲突很多的话,一些索引维护操作的代价也会很高。例如,如果在某个选择性很低(哈希冲突很多)的列上建立哈希索引,那么当从表中删除一行时,存储引擎需要遍历对应哈希值的链表中的每一行,找到并删除对应行的引用,冲突越多,代价越大。

因为这些限制,哈希索引只适用于某些特定的场合。而一旦适合哈希索引,则它带来的性能提升将非常显著。举个例子,在数据仓库应用中有一种经典的“星型” schema,需要关联很多查找表,哈希索引就非常适合查找表的需求。

除了 Memory引擎外,NDB集群引擎也支持唯一哈希索引,且在NDB集群引擎中作用非常特殊,但这不属于本书的范围。

InnoDB引擎有一个特殊的功能叫做“自适应哈希索引( adaptive hash index)”。当InnoDB注意到某些索引值被使用得非常频繁时,它会在内存中基于B-Tree索引之上再创建一个哈希索引,这样就让 B-Tree索引也具有哈希索引的一些优点,比如快速的哈希查找。这是一个完全自动的、内部的行为,用户无法控制或者配置,不过如果有必要完全可以关闭该功能

创建自定义哈希索引。如果存储引擎不支持哈希索引,则可以模拟像 InnoDB一样创建哈希索引,这可以享受一些哈希索引的便利,例如只需要很小的索引就可以为超长的键创建索

案例:如果某一列(如url)太长不适合建立索引,可以增加一列,这列是url哈希的结果,拿他建立索引。至于维护问题,可以创建触发器。好用的哈希是crc32值索引(返回32位整数),记住不要使用SHA1()和MD5()作为哈希函数,因为这个链各个函数计算出来的哈希值是非常长的字符串,会浪费大量空间。SHA1和MD5是强加密函数,设计目标是最大限度消除冲突,但这里并不需要这样高的要求。简单哈希函数的冲突在一个可以接受的范围,同时又能提供更好的性能。也可以自己涉及一个64位的哈希函数。这个自定义函数要返回整数,而不是字符串。可以使用MD5函数返回值的一部分作为自定义哈希函数。也可以直接使用长的列的一部分作为索引(前缀索引)。

ALTER TABLE A ADD KEY (city(7)); -- 前缀为7建立索引

前缀索引是一种能使索引更小、更快的有效方法,但另一方面也有其缺点:Mysql无法使用前缀索引做ODER BY和GROUP BY,也无法使用前缀索引做覆盖扫描。

前缀索引和索引选择性

索引选择性:不重复的索引值/记录总数

对于BLOB、TEXT、很长的VARCHAR类型列,必须使用前缀索引,因为mysql不允许索引这些列的完整长度

取多长:这个长度的前缀的选择性接近完整列的选择性

select count(*) as cnt,left(city,7) as pref
from city group by pref order by cnt desc limit 10;

select count(distinct left(city,3))/count(*) as sel3,
count(distinct left(city,4))/count(*) as sel4,
count(distinct left(city,5))/count(*) as sel5,
count(distinct left(city,6))/count(*) as sel6,
from city;

索引列顺序

将选择性搞的列放到索引最前列。这在不需要考虑排序和分组时,是很好的,这时候索引的作用只是用于优化where条件的查找。

第 6 章 Explain 性能分析

1. 概念

使用 EXPLAIN 关键字可以模拟优化器执行 SQL 查询语句, 从而知道 MySQL 是如何处理你的 SQL 语句的。 分析你的查询语句或是表结构的性能瓶颈

用法: Explain+SQL 语句。

Explain 执行后返回的信息:

id
select_type
table
partitions(5.7后,分区命中情况)
type
possible_keys
key
key_len
ref
rows
filtered(5.7)
Extra

2. Explain 准备工作(建表sql)

CREATE TABLE t1(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
CREATE TABLE t2(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
CREATE TABLE t3(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
CREATE TABLE t4(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
INSERT INTO t1(content) VALUES(CONCAT('t1_',FLOOR(1+RAND()*1000)));
INSERT INTO t2(content) VALUES(CONCAT('t2_',FLOOR(1+RAND()*1000)));
INSERT INTO t3(content) VALUES(CONCAT('t3_',FLOOR(1+RAND()*1000)));
INSERT INTO t4(content) VALUES(CONCAT('t4_',FLOOR(1+RAND()*1000)));

新建三张表,分别是课程表,老师表,老师卡片表。课程表记录课程与上课老师。老师卡片记录着老师的其他信息。

create table course(
    cid int(3),
    cname varchar(20),
    tid int(3)
);
create table teacher(
    tid int(3),
    tname varchar(20),
    tcid int(3)
);
create table teacherCard(
    tcid int(3),
    tcdesc varchar(200)
);

insert into course values(1,'java',1);
insert into course values(2,'html',1);
insert into course values(3,'sql',2);
insert into course values(4,'web',3);

insert into teacher values(1,'tz',1);
insert into teacher values(2,'tw',2);
insert into teacher values(3,'tl',3);

insert into teacherCard values(1,'tzdesc') ;
insert into teacherCard values(2,'twdesc') ;
insert into teacherCard values(3,'tldesc') ;

select * from teacher;
+------+-------+------+
| tid  | tname | tcid |
+------+-------+------+
|    1 | tz    |    1 |
|    2 | tw    |    2 |
|    3 | tl    |    3 |
+------+-------+------+
select * from teacherCard;
+------+--------+
| tcid | tcdesc |
+------+--------+
|    1 | tzdesc |
|    2 | twdesc |
|    3 | tldesc |
+------+--------+
select * from course;
+------+-------+------+
| cid  | cname | tid  |
+------+-------+------+
|    1 | java  |    1 |
|    2 | html  |    1 |
|    3 | sql   |    2 |
|    4 | web   |    3 |
+------+-------+------+

3. id

select 查询的序列号,包含一组数字, 表示查询中执行 select 子句或操作表的顺序

①id 相同:执行顺序由上至下

②id 不同:id 值大的先被执行。 如果是子查询, id 的序号会递增 (本质:在嵌套子查询时,先查内层 再查外层)

③有相同也有不同:id 如果相同, 可以认为是一组, 从上往下顺序执行; 在所有组中, id 值越大, 优先级越高, 越先执行衍生 = DERIVED

数据小的表 优先查询;

表的执行顺序 因数量的个数改变而改变的原因: 笛卡儿积

? a b c
? 4 3 2 = 2*3=6 * 4 =24
? 3*4=12* 2 =24

查询教授SQL课程的老师的描述(descexplain select tc.tcdesc from teacherCard tc,course c,teacher t where c.tid = t.tid
and t.tcid = tc.tcid and c.cname = 'sql' ;
-- 思路:连接条件是课程与老师连接,老师与老师卡连接,课程名为sql


将以上 多表查询 转为子查询形式:
-- 查询教授SQL课程的老师的描述(desc)
explain select tc.tcdesc from teacherCard tc where tc.tcid = 
(select t.tcid from teacher t where  t.tid = 
	(select c.tid from course c where c.cname = 'sql')
);
-- 思路:最外层语句是老师的描述,描述是通过tcid去找sql的,所以需要先拿teacher表过渡,让tcid=某些teacher表的tcid

子查询+多表: 
explain select t.tname ,tc.tcdesc from teacher t,teacherCard tc where t.tcid= tc.tcid
and t.tid = (select c.tid from course c where cname = 'sql') ;

关注点:id 号每个号码, 表示一趟独立的查询。 一个 sql 的查询趟数越少越好

mysql> explain select * from teacher,course,teacherCard where course.tid=teacher.tid and teacher.tcid=teacherCard.tcid \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: teacher
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 2
     filtered: 100.00
        Extra: NULL
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: teacherCard
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 2
     filtered: 50.00
        Extra: Using where; Using join buffer (Block Nested Loop)
*************************** 3. row ***************************
           id: 1
  select_type: SIMPLE
        table: course
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 4
     filtered: 25.00
        Extra: Using where; Using join buffer (Block Nested Loop)
3 rows in set, 1 warning (0.00 sec)
-------------------------
启示:id相同,from写的顺序是teacher,course,teacherCard,而实际上是teacher,teacherCard,course,原因是进行笛卡尔积的时候,小表在前

4. select_type

select_type 代表查询的类型, 主要是用于区别普通查询、 联合查询、 子查询等的复杂查询。

select_type 属性 含义
SIMPLE 简单的 select 查询,查询中不包含子查询或者 UNION
PRIMARY 查询中若包含任何复杂的子部分, 最外层查询则被标记为 Primary
DERIVED FROM 列表中包含的子查询被标记为 DERIVED(衍生) MySQL 会递归执行这些子查询, 把结果放在临时表里。
SUBQUERY SELECT或WHERE列表中包含了子查询
DEPEDENT SUBQUERY 在SELECT或WHERE列表中包含了子查询,子查询基于外层
UNCACHEABLE SUBQUERY 无法使用缓存的子查询
UNION 若第二个SELECT出现在UNION之后, 则被标记为UNION; 若UNION包含在FROM子句的子查询中,外层SELECT将被标记为: DERIVED
UNION RESULT 从UNION表获取结果的SELECT

4.1 SIMPLE

SIMPLE 代表单表查询;

简单的select查询,查询中不包含子查询或者union

explain select * from teacher\G
--为了排版不乱,在末尾用\G就代表输出成竖版格式,1.row代表第一行。有了\G就无需在结尾写分号;了
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE --这里显示SIMPLE,代表只是一个单表查询
        table: teacher
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 3
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.00 sec)

4.2 PRIMARY

查询中包含任何复杂的子部分,最外层查询则被标记为Primary。(最后执行的语句)

explain select * from teacher where teacher.tid=(select * from course)\G
-- from部分的临时表必须有别名
-- 注意,在mysql5.5中查询此时select_type应该是PRIMARY和DERIVED,在mysql5.7中可能进行了优化。只显示SIMPLE
-- 这句是不是不对?
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: teacher
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 3
     filtered: 100.00
        Extra: NULL
------------------------------
mysql> explain select * from teacher where teacher.tid=(select course.tid from course)\G
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: teacher
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 2
     filtered: 50.00
        Extra: Using where
*************************** 2. row ***************************
           id: 2
  select_type: SUBQUERY -- 因为子查询在where里
        table: course
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 4
     filtered: 100.00
        Extra: NULL
2 rows in set, 1 warning (0.00 sec)

4.3 DERIVED

在 FROM 列表中包含的子查询被标记为 DERIVED(衍生),MySQL 会递归执行这些子查询, 把结果放在临时表里。

  • 在from后面的子查询,有且只有一张表

    select cr.cname from (select * from course where tid in (1,2) cr)

  • 在from后面的子查询,如果有table1 union table2,则table1是derived,table2就是union表

    select cr.name from (select * from course where tid=1 union select * from course where tid=2) cr


4.4 SUBQUERY

在 SELECT 或 WHERE 列表中包含了子查询。

EXPLAIN SELECT t2.id FROM t2 WHERE t2.id=
(SELECT t3.id FROM t3 WHERE t3.id=2)

t2是PRIMARY t3是SUBQUERY

t3的select_type是SUBQUERY,代表这个查询

4.5 DEPENDENT SUBQUERY

在 SELECT 或 WHERE 列表中包含了子查询,子查询基于外层

EXPLAIN
SELECT t2.id FROM t2 WHERE t2.id IN
(SELECT t3.id FROM t3.content='PfkHFe')

都是 where 后面的条件, subquery 是单个值, dependent subquery 是一组值

4.6 UNCACHEABLE SUBQUREY

当使用了@@来引用系统变量的时候, 不会使用缓存。

4.7 UNION

若第二个 SELECT 出现在 UNION 之后, 则被标记为 UNION; 若 UNION 包含在 FROM 子句的子查询中,外层 SELECT将被标记为: DERIVED。

mysql> select tcid from teacher union select tcid from teacherCard;
+------+
| tcid |
+------+
|    1 |
|    2 |
|    3 |
+------+
3 rows in set (0.00 sec)
---------------------------------------
mysql> explain select tcid from teacher union select tcid from teacherCard\G
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: teacher
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 2
     filtered: 100.00
        Extra: NULL
*************************** 2. row ***************************
           id: 2
  select_type: UNION
        table: teacherCard
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 2
     filtered: 100.00
        Extra: NULL
*************************** 3. row ***************************
           id: NULL
  select_type: UNION RESULT
        table: <union1,2>
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: NULL
     filtered: NULL
        Extra: Using temporary
3 rows in set, 1 warning (0.00 sec)

4.8 UNION RESULT

从 UNION 表获取结果的 SELECT。

5. table

这个数据是基于哪张表的。

6. type(索引类型)

https://blog.csdn.net/dennis211/article/details/78170079

type意味着类型,这里的type官方全称是“join type”,意思是“连接类型”,这样很容易给人一种错觉觉得必须需要俩个表以上才有连接类型。事实上这里的连接类型并非字面那样的狭隘,它更确切的说是一种数据库引擎查找表的一种方式,在《高性能mysql》一书中作者更是觉得称呼它为访问类型更贴切一些。

mysql5.7中type的类型达到了14种之多,这里只记录和理解最重要且经常遇见的六种类型,它们分别是all,index,range,ref,eq_ref,const。从左到右,它们的效率依次是增强的。撇开sql的具体应用环境以及其他因素,你应当尽量优化你的sql语句,使它的type尽量靠右,但实际运用中还是要综合考虑各个方面的。

要对type优化的前提:有索引。

type 是查询的访问类型。 是较为重要的一个指标, 结果值从最好到最坏依次是:

system > const > eq_ref > 

ref > fulltext > ref_or_null > index_merge > 

unique_subquery > index_subquery > r

ange > index >    ALL , 

一般来说, 得保证查询至少达到 range 级别, 最好能达到 ref 。system,const只是理想情况;实际能达到 ref>range

6.1 system

表只有一行记录(等于系统表), 这是 const 类型的特列, 平时不会出现, 这个也可以忽略不计

只有一条数据的系统表,或衍生表只有一条数据的主查询。

select * from (select * from test01) t where tid=1(在5.7中不行)

6.2 const

表示通过索引一次就找到了,const 用于比较 primary key 或者 unique 索引。 因为只匹配一行数据, 所以很快。(如where id=1)。(表数据任意行)

如将主键置于 where 列表中, MySQL 就能将该查询转换为一个常量。

mysql> explain select * from (select * from test01 )t where tid =1 \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: test01
   partitions: NULL
         type: const -- 因为我们表中只有一条数据
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: const
         rows: 1
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.00 sec)

mysql> create unique index index1 on teacher(tid);-- 创建唯一索引
mysql> explain select * from (select * from teacher )t where tid =1 \G 
-- 被自动优化了?跟select * from teacher where tid=1;应该一样
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: teacher
   partitions: NULL
         type: const
possible_keys: index1
          key: index1
      key_len: 5
          ref: const
         rows: 1
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.00 sec)

mysql const与eq_ref的区别
简单地说是const是直接按主键或唯一键读取,eq_ref用于联表查询的情况,按联表的主键或唯一键联合查询。

下面的内容翻译自官方方档:

const
该表最多有一个匹配行, 在查询开始时读取。由于只有一行, 因此该行中列的值可以被优化器的其余部分视为常量。const 表非常快, 因为它们只读一次。

const用于将 "主键" 或 "唯一" 索引的所有部分与常量值进行比较。在下面的查询中, tbl_name 可以用作 const 表:

SELECT * FROM tbl_name WHERE primary_key=1;

SELECT * FROM tbl_name
  WHERE primary_key_part1=1 AND primary_key_part2=2;
  
eq_ref
读取本表中和关联表表中的每行组合成的一行。除了 system 和 const 类型之外, 这是最好的联接类型。当连接使用索引的所有部分时, 索引是主键或唯一非 NULL 索引时, 将使用该值。
eq_ref 可用于使用 = 运算符比较的索引列。比较值可以是常量或使用此表之前读取的表中的列的表达式。在下面的示例中, MySQL 可以使用 eq_ref 连接(join)ref_table来处理:

 
SELECT * FROM ref_table,other_table
  WHERE ref_table.key_column=other_table.column;

SELECT * FROM ref_table,other_table
  WHERE ref_table.key_column_part1=other_table.column
  AND ref_table.key_column_part2=1;

6.3 eq_ref

ref_eq 与 ref相比牛的地方是,它知道这种类型的查找结果集只有一个?什么情况下结果集只有一个呢!那便是使用了主键或者唯一性索引进行查找的情况,比如根据学号查找某一学校的一名同学,在没有查找前我们就知道结果一定只有一个,所以当我们首次查找到这个学号,便立即停止了查询。这种连接类型每次都进行着精确查询,无需过多的扫描,因此查找效率更高,当然列的唯一性是需要根据实际情况决定的。

mysql> explain select ep.name,sc.mark from employee ep,score sc where ep.rec_id = sc.stu_id;

+----+-------------+-------+--------+-----------------+---------+---------+-----------------+------+-------+
| id | select_type | table | type   | possible_keys   | key     | key_len | ref             | rows | Extra |
+----+-------------+-------+--------+-----------------+---------+---------+-----------------+------+-------+
|  1 | SIMPLE      | sc    | ALL    | UK_SCORE_STU_ID | NULL    | NULL    | NULL            |    5 | NULL  |
|  1 | SIMPLE      | ep    | eq_ref | PRIMARY         | PRIMARY | 4       | my_db.sc.stu_id |    1 | NULL  |
+----+-------------+-------+--------+-----------------+---------+---------+-----------------+------+-------+

就是说返回的组合起来的结果集只有一条,但是笛卡尔积是一一对应的,而且我们是提前知道的

employee表中有五条数据,score表中有对应的五条数据,其中employee的rec_id 和score的stu_id 是一一对应的。

name得是索引。常见于唯一索引或主键索引

是可遇不可求的,所以达到ref即可

6.4 ref(尽量)

出现该连接类型的条件是: 查找条件列使用了索引但这个索引不是主键和unique。如给name建立索引,每个索引可能对应几个返回结果

其实,意思就是虽然使用了索引,但该索引列的值并不唯一,有重复。这样即使使用索引快速查找到了第一条数据,仍然不能停止,要进行目标值附近的小范围扫描。但它的好处是它并不需要扫全表,因为索引是有序的,即便有重复值,也是在一个非常小的范围内扫描。下面为了演示这种情形,给employee表中的name列添加一个普通的key(值允许重复)

比如建立name索引后,select * from teacher where name=‘tz’;

name不加索引时是ALL

6.5 range(至少)

range指的是有范围的索引扫描,相对于index的全索引扫描,它有范围限制,因此要优于index。

关于range比较容易理解,需要记住的是出现了range,则一定是基于索引的。同时除了显而易见的between,and以及’>’,’<'外,in和or也是索引范围扫描。

只检索给定范围的行,使用一个索引来选择行。 key 列显示使用了哪个索引一般就是在你的 where 语句中出现了 between、 <、 >、 in 等的查询这种范围扫描索引扫描比全表扫描要好, 因为它只需要开始于索引的某一点, 而结束语另一点, 不用扫描全部索引。主键索引和普通索引都是这个。

in和数据量有关,大于数据量一般就会全表扫描 。即这里所包含的数据不能大于表的一般,不然in会失效成all

mysql> EXPLAIN SELECT * FROM t1 WHERE t1.id<10 \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
   partitions: NULL
         type: range
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: NULL
         rows: 1
     filtered: 100.00
        Extra: Using where
1 row in set, 1 warning (0.00 sec)
----------------------
EXPLAIN SELECT * FROM t1 WHERE t1.id IN (10,13,33);

6.6 index

这种连接类型只是另外一种形式的全表扫描,只不过它的扫描顺序是按照索引的顺序使用了索引但是没用通过索引进行过滤

我刚开始也会疑惑上句到底是什么意思,后来才明白,是因为没有加where语句,比如SELECT id FROM t1这句查询语句,id是我们的索引,但是没有where过滤,所以我们的B+树并没有什么用,还是像链表一样从头到尾扫描。在B+树结构中,

这种扫描根据索引然后回表取数据,和all相比,他们都是取得了全表的数据,而且index要先读索引而且要回表随机取数据我们知道在索引的叶子结点有一个叶子结点之间的双向指针, 并且叶子结点的数据是排序好的。他和ALL的方式类似,访问效率并不高,其主要的应用场景为用于避免order by使用using filesort也就是避免排序。他是一种访问数据的方式,和const、ref、eq_ref等一样

出现index是sql,一般是使用了覆盖索引或者是利用索引进行了排序分组

index代表查询索引的数据,没有查询费索引的数据,比如查了id没有查name,只需要扫描索引表,不需要扫描表全部数据。而all是查询表的数据。

mysql> EXPLAIN SELECT * FROM t1 \G
*************************** 1. row ***************************
         type: ALL -- 注意
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 1
     filtered: 100.00
        Extra: NULL

mysql> EXPLAIN SELECT id FROM t1 \G
*************************** 1. row ***************************
         type: index -- 注意
possible_keys: NULL
          key: PRIMARY
      key_len: 4
          ref: NULL
         rows: 1
     filtered: 100.00
        Extra: Using index

6.7 all

这便是所谓的“全表扫描”,没有经过索引

为什么这么说呢?因为all是一种非常暴力和原始的查找方法,非常的耗时而且低效。用all去查找数据就好比这样的一个情形:S学校有俩万人,我告诉你你给我找到小明,然后你怎么做呢!你当然是把全校俩万人挨个找一遍,即使你很幸运第一个人便找到了小明,但是你仍然不能停下,因为你无法确认是否有另外一个小明存在,直到你把俩万人找完为止。所以,基本所有情况,我们都要避免这样类型的查找,除非你不得不这样做。

-- 不加索引时
explain select * from t1,t2 where t1.content=t2.content;

这是因为content列既不是主键也不是索引,因此只能采用全表扫描来查找目标content。

6.8 index_merge

在查询过程中需要多个索引组合使用, 通常出现在有 or 的关键字的 sql 中 。(因为一个索引就够了,另外一个索引就多余了)

6.9 ref_or_null

对于某个字段既需要关联条件, 也需要 null 值得情况下。 查询优化器会选择用ref_or_null 连接查询。

6.10 index_subquery

利用索引来关联子查询, 不再全表扫描。

CREATE INDEX index_ctnt ON t3(content);

6.11 unique_subquery

该联接类型类似于 index_subquery。 子查询中的唯一索引。

7. possible_keys

可能使用的索引。

显示可能应用在这张表中的索引, 一个或多个。 查询涉及到的字段上若存在索引, 则该索引将被列出, 但不一定被查询实际使用

也有可能理论上没有,但实际使用了,如select col1,col2 from t1;

8. key

实际使用的索引。 如果为NULL, 则没有使用索引。

覆盖索引:select col1,col2 from t1;

9. key_len

表示索引中使用的字节数, 可通过该列计算查询中使用的索引的长度。 key_len 字段能够帮你==检查是否充分的利用上了索引。 ken_len 越长, 说明索引使用的越充分==

用于处理查询的索引长度,如果是单列索引,那就整个索引长度算进去,如果是多列索引,那么查询不一定都能使用到所有的列,具体使用到了多少个列的索引,这里就会计算进去,没有使用到的列,这里不会计算进去。留意下这个列的值,算一下你的多列索引总长度就知道有没有使用到所有的列了。要注意,mysql的ICP特性使用到的索引不会计入其中。另外,key_len只计算where条件用到的索引长度,而排序order by和分组group by就算用到了索引,也不会计算到key_len中。

用于判断复合索引是否被完全使用。

utf8一个字符是三个字节,gbk是2个字节

我们使用最常用的utf-8编码按如下几点可以进行判断:

  • 1、字段类型 int为4个,date为3,datetime为4,char(n)为3×n,varchar(n)为3×n+2
  • 2、如果字段可为 null,则需要额外再加1
  • 如果显示可变长度,那么加2,最后最终varchar(n)为3×n+2

表的列一个为var20不能为空,一个可以为空。如索引字段可以为null,则会使用一个字节用于标识。所以可能是61。60是因为使用了utf8编码,原来是20个字符,需要60个字节编码。用两个字节标识可变长度

如果name是not null,查出来可能是60,而name可以为null,则查出来是61

索引需要挨个字段使用,所以比如复合索引包括name,name1,那么使用name=’’(not null)查的时候,显示的是60,而使用name1=’’(可空)查的时候显示的是121

如何计算:

①先看索引上字段的类型+长度比如 int=4 ; varchar(20) =20 ; char(20) =20

②如果是 varchar 或者 char 这种字符串字段, 视字符集要乘不同的值, 比如 utf-8 要乘 3,GBK 要乘 2,

③varchar 这种动态字符串要加 2 个字节

④允许为空的字段要加 1 个字节

第一组: key_len=age 的字节长度+name 的字节长度=4+1 + ( 20*3+2)=5+62=67

第二组: key_len=age 的字节长度=4+1=5

10. ref

显示索引的哪一列被使用了, 如果可能的话, 是一个常数。 哪些列或常量被用于查找索引列上的值

  • 如果是使用的常数等值查询,这里会显示const
  • 如果是连接查询,被驱动表的执行计划这里会显示驱动表的关联字段
  • 如果是条件使用了表达式或者函数,或者条件列发生了内部隐式转换,这里可能显示为func

指明当前表所 参照的字段

select...where a.c=b.x(其中b.x可以是常量,const。注意a.c得是索引)

列与索引的比较,表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值

11. rows

rows 列显示 MySQL 认为它执行查询时必须检查的行数。 越少越好!

根据统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数

被索引优化查询的数据个数 ,实际通过索引查询到的数据个数

12. Extra

其他的额外重要的信息

12.1 Using filesort

查询中排序的字段, 排序字段若通过索引去访问将大大提高排序速度。

在使用order by关键字的时候,如果待排序的内容不能由所使用的索引直接完成排序的话,那么mysql有可能就要进行文件排序。这时候就出现了using filesort

排序:先查询再排序。先where再执行order by

# 单索引
select * from test where a1='' order by a1;就没事

select * from test where a1='' order by a2;此时需要额外的一次查询a2。

# 复合索引,跨列了就会出现
alter table test add index idx_123 (a1,a2,a3);
select * from test where a1='' order by a3;

常见场景:

  • 单索引,如果排序和查找是同一个字段,则不会出现。反之会出现using filesort

  • 复合索引:不能跨列(最佳左前缀)

复合索引例子

alter table test add index idx_123 (a1,a2,a3);
select * from test where a1='' order by a3;

这个 filesort 并不是说通过磁盘文件进行排序,而只是告诉我们进行了一个排序操作而已】。

只有在order by数据列的时候才可能会出现using filesort,而且如果你不对进行order by的这一列设置索引的话,无论列值是否有相同的都会出现using filesort。因此,只要用到order by 的这一列都应该为其建立一个索引。并且还不能是order by列的单一索引,而是需要和where的列组成组合索引

当然,using filesort不一定引起mysql的性能问题。但是如果查询次数非常多,那么每次在mysql中进行排序,还是会有影响的。
此时,可以进行的优化:
1、修改逻辑,不在mysql中使用order by而是在应用中自己进行排序。
2、使用mysql索引,将待排序的内容放到索引中,直接利用索引的排序。

explain SELECT * FROM table_item WHERE user_id = 2 ORDER BY item_id LIMIT 0, 5

user_id 和 item_id 分别建立一个索引,对此语句MySQL选择了 user_id索引,那么 item_id 的索引没有起到任何用处(所以应该把where列和order by列组成组合索引)。当排序时记录数较多,内存中的排序 buffer满了,只能 Using filesort 进行外部排序。

解决方式或许为对这两列建立组合索引

explain SELECT * FROM table_item WHERE user_id = 2 and user_age > 20 ORDER BY item_id LIMIT 0, 5

建立组合索引(user_id, user_age, item_id),看似非常完美。但由于user_age规则不是确定值,使用该组合索引的话实际上需要先按索引找出一个个user_age下的东西后再对其进行排序,仍然会filesort。

解决方式是使用组合索引(user_id, item_id),对排序好的item_id再过滤到不满足user_age > 20的条目,不会出现filesort

explain SELECT * FROM table_item WHERE user_id = 2 ORDER BY item_attr desc, item_id LIMIT 0, 5

建立了组合索引(user_id, item_attr, item_id),因为item_attr是降序而item_id是升序,从而仍然需要外部排序。

如果item_attr和item_id都是升序或者都是降序,则不会出现filesort;建立组合索引(user_id, item_attr desc, item_id)现象依旧。

Mysql5.1在线文档“13.1.4. CREATE INDEX语法”中提到:“一个index_col_name规约可以以ASC或DESC为结尾。这些关键词将来可以扩展,用于指定递增或递减索引值存储。目前,这些关键词被分析,但是被忽略;索引值均以递增顺序存储。”
可以看出无法通过修改索引来避免这个filesort,只能是如果可能的话修改查询的排序条件。

为什么不建立三索引:

原来在多列索引在建立的时候是以B树结构建立的,因此建立索引的时候是先建立ID的按顺序排的索引,在相同ID的情况下建立FID按 顺序排的索引,最后在FID 相同的情况下建立按INVERSE_DATE顺序排的索引,如果列数更多以此类推。有了这个理论依据我们可以看出在这个sql使用这个IDX索引的时候只是用在了order by之前,order by INVERSE_DATE 实际上是using filesort出来的。。汗死了。。因此如果我们要在优化一下这个sql就应该为它建立另一个索引IDX(ID,INVERSE_DATE),这样就消除了using filesort速度也会快很多。问题终于解决了。

12.2 Using temporary

需要先了解顺序

from
on
jion
where
group by
having
select
distinct
order by
limit

用到了临时表。一般出现在group by语句中。已经有表了,但不适用,必须再来一张表。

select a1 from test where a1 in ('1','2','3') group by a1; -- 此时没有Using temporary
select a1 from test where a1 in ('1','2','3') group by a1,a2;-- 此时没有Using temporary

select a1 from test where a1 in ('1','2','3') group by a2;-- 此时有Using temporary

如何避免:查询哪些列,就根据那些列group by,group by后面跟的索引列的顺序要按序写

使了用临时表保存中间结果,MySQL 在对查询结果排序时使用临时表。 常见于排序 order by 和分组查询 group by。

12.3 Using index

性能提升了。索引覆盖,覆盖索引。原因:此次查询不读取原文件,只查询索引表,查的是B树。只要适用到的列全部都在索引中,就是索引覆盖。

例如:在test表中有一个复合索引(a1,a2,a3)。

如果用到了索引覆盖using index,会对possible_keys和key造成影响

  • 如果没有where,则索引只出现在key中
  • 如果有where,则索引出现在key和possible_key中

Using index 代表表示相应的 select 操作中使用了覆盖索引(Covering Index), 避免访问了表的数据行, 效率不错!

如果同时出现 using where, 表明索引被用来执行索引键值的查找;(索引的字段不够用)

如果没有同时出现 using where, 表明索引只是用来读取数据而非利用索引执行查找。(要查的内容索引字段中就有)

利用索引进行了排序或分组。

12.4 Using where

需要在索引和原表中查。select age,name from…where age=

表明使用了 where 过滤。

查询的列未被索引覆盖,where筛选条件非索引的前导列

12.5 Using join buffer

使用了连接缓存。

12.6 impossible where

where 子句的值总是 false, 不能用来获取任何元组。即where写错了

12.7 select tables optimized away

在没有 GROUPBY 子句的情况下, 基于索引优化 MIN/MAX 操作或者对于 MyISAM 存储引擎优化 COUNT(*)操作, 不必等到执行阶段再进行计算, 查询执行计划生成的阶段即完成优化。

在 innodb 中:

在myisam中:

第 7 章 批量数据脚本

1. 插入数据

1.1 建表语句

部门表 员工表
CREATE TABLE `dept` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `deptName` VARCHAR(30) DEFAULT NULL,
    `address` VARCHAR(40) DEFAULT NULL,
    ceo INT NULL ,
    PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE `emp` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `empno` INT NOT NULL ,
    `name` VARCHAR(20) DEFAULT NULL,
    `age` INT(3) DEFAULT NULL,
    `deptId` INT(11) DEFAULT NULL,
    PRIMARY KEY (`id`)
    #CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `t_dept` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

1.2 设置参数

在执行创建函数之前, 首先请保证 log_bin_trust_function_creators 参数为 1, 即 on 开启状态。
否则会报错 :

查询: show variables like 'log_bin_trust_function_creators';
设置:set global log_bin_trust_function_creators=1;
当然, 如上设置只存在于当前操作, 想要永久生效, 需要写入到配置文件中:
在[mysqld]中加上 log_bin_trust_function_creators=1

1.3 编写随机函数

创建函数, 保证每条数据都不同。

函数有返回值,存储过程没有返回值

1.3.1 随机产生字符串
DELIMITER $$
CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255)
BEGIN
DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
DECLARE return_str VARCHAR(255) DEFAULT '';
DECLARE i INT DEFAULT 0;
WHILE i < n DO
SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
SET i = i + 1;
END WHILE;
RETURN return_str;
END $$

如果要删除函数, 则执行: drop function rand_string;

1.3.2 随机产生部门编号
#用于随机产生多少到多少的编号
DELIMITER $$
CREATE FUNCTION rand_num (from_num INT ,to_num INT) RETURNS INT(11)
BEGIN
DECLARE i INT DEFAULT 0;
SET i = FLOOR(from_num +RAND()*(to_num -from_num+1)) ;
RETURN i;
END$$

如果要删除函数: drop function rand_num;

1.4 创建存储过程

1.4.1 创建往 emp 表中插入数据的存储过程
DELIMITER $$
CREATE PROCEDURE insert_emp( START INT , max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
#set autocommit =0 把 autocommit 设置成 0
SET autocommit = 0;
REPEAT
SET i = i + 1;
—————————————————————————————————————

INSERT INTO emp (empno, NAME ,age ,deptid ) VALUES ((START+i) ,rand_string(6) ,
rand_num(30,50),rand_num(1,10000));
UNTIL i = max_num
END REPEAT;
COMMIT;
END$$
#删除
# DELIMITER ;
# drop PROCEDURE insert_emp;
1.4.2 创建往 dept 表中插入数据的存储过程
#执行存储过程, 往 dept 表添加随机数据
DELIMITER $$
CREATE PROCEDURE `insert_dept`( max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO dept ( deptname,address,ceo ) VALUES (rand_string(8),rand_string(10),rand_num(1,500000));
UNTIL i = max_num
END REPEAT;
COMMIT;
END$$
#删除
# DELIMITER ;
# drop PROCEDURE insert_dept;

1.5 调用存储过程

1.5.1 添加数据到部门表
#执行存储过程, 往 dept 表添加 1 万条数据
DELIMITER ;
CALL insert_dept(10000);
1.5.2 添加数据到员工表
#执行存储过程, 往 emp 表添加 50 万条数据
DELIMITER ;
CALL insert_emp(100000,500000);

1.6 批量删除某个表上的所有索引

1.6.1 删除索引的存储过程
DELIMITER $$
CREATE PROCEDURE `proc_drop_index`(dbname VARCHAR(200),tablename VARCHAR(200))
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE ct INT DEFAULT 0;
DECLARE _index VARCHAR(200) DEFAULT '';
DECLARE _cur CURSOR FOR SELECT index_name FROM information_schema.STATISTICS WHERE
table_schema=dbname AND table_name=tablename AND seq_in_index=1 AND index_name <>'PRIMARY' ;
DECLARE CONTINUE HANDLER FOR NOT FOUND set done=2 ;
OPEN _cur;
FETCH _cur INTO _index;
WHILE _index<>'' DO
SET @str = CONCAT("drop index ",_index," on ",tablename );
PREPARE sql_str FROM @str ;
EXECUTE sql_str;
DEALLOCATE PREPARE sql_str;
SET _index='';
FETCH _cur INTO _index;
END WHILE;
CLOSE _cur;
END$$
1.6.2 执行存储过程

调用: CALL proc_drop_index(“dbname”,“tablename”);

第 8 章 单表使用索引常见的索引失效

1. 全值匹配

1.1 有以下 SQL 语句

EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=4
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=4 AND emp.name = 'abcd'

1.2 建立索引

CREATE INDEX idx_age_deptid_name ON emp(age,deptid,NAME);

然后再查询

EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=4
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=4 AND emp.name = 'abcd'
此时的结果显示3条explain的type都是ref,用到的索引是idx_age_deptid_name

结论: 全局匹配我最爱指的是, 查询的字段按照顺序在索引中都可以匹配到

explain select SQL_NO_CACHE * from emp where emp.age=30 and emp.name='abcd' and depId=4;
explain select SQL_NO_CACHE * from emp where emp.age=30 and depId=4 and emp.name='abcd';
两句虽然调换了索引的使用顺序,但是结果相同,说明mysql自动优化了

SQL 中查询字段的顺序, 跟使用索引中字段的顺序, 没有关系。 优化器会在不影响 SQL 执行结果的前提下, 给你自动地优化

2. 最佳左前缀法则

explain select SQL_NO_CACHE * from emp  where depId=4 and emp.name='abcd'; 

查询字段与索引字段顺序的不同会导致, 索引无法充分使用, 甚至索引失效!

原因: 使用复合索引, 需要遵循最佳左前缀法则, 即如果索引了多列, 要遵守最左前缀法则。 指的是查询从索引的最左前列开始并且不跳过索引中的列。

结论: 过滤条件要使用索引必须按照索引建立时的顺序, 依次满足, 一旦跳过某个段, 索引后面的字段都无法被使用。

3. 不要在索引列上做任何计算

不在索引列上做任何操作(计算、 函数、 (自动 or 手动)类型转换), 会导致索引失效而转向全表扫描

3.1 在查询列上使用了函数

EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE age=30;
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE LEFT(age,3)=30; 

结论: 等号左边无计算!

3.2 在查询列上做了转换

create index idx_name on emp(name);
explain select sql_no_cache * from emp where name='30000';
explain select sql_no_cache * from emp where name=30000;

字符串不加单引号, 则会在 name 列上做一次转换

结论:等号右边无转换

4. 索引列上不能有范围查询

这里他想表达的意思是

explain SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=5 AND emp.name = 'abcd';

explain SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid<=5 AND emp.name = 'abcd';

建议: 将可能做范围查询的字段的索引顺序放在最后

5. 尽量使用覆盖索引

即查询列和索引列一致, 不要写select *

explain SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptId=4 and name='XamgXt';

explain SELECT SQL_NO_CACHE age,deptId,name FROM emp WHERE emp.age=30 and deptId=4 and name='XamgXt';

6. 使用不等于(!= 或者<>)的时候

mysql 在使用不等于(!= 或者<>)时, 有时会无法使用索引,会导致全表扫描

7. 字段的 is not null 和 is null

当字段允许为NULL的条件下:

is not null 用不到索引, is null 可以用到索引

is null也是索引

8. like 的前后模糊匹配

模糊查询like,’%a%’,在a前出现%会使索引失效

前缀不能出现模糊匹配!

如果必须要用模糊查询,可以倒序,也可以select部分和where都索引内容

9. 减少使用 or

使用or会使索引失效,应该使用union代替。

使用 union all 或者 union 来替代:

10. 练习

假设 index(a,b,c);

Where 语句 索引是否被使用
where a = 3 Y,使用到 a
where a = 3 and b = 5 Y,使用到 a,b
where a = 3 and b = 5 and c = 4 Y,使用到 a,b,c
where b = 3 或者 where b = 3 and c = 4 或者 where c = 4 N
where a = 3 and c = 5 使用到 a, 但是 c 不可以, b 中间断了
where a = 3 and b > 4 and c = 5 使用到 a 和 b, c 不能用在范围之后, b 断了
where a is null and b is not null is null 支持索引 但是 is not null 不支持,所 以 a 可以使用索引,但是 b 不可以使用
where a <> 3 不能使用索引
where abs(a) =3 不能使用 索引
where a = 3 and b like ‘kk%’ and c = 4 Y,使用到 a,b,c
where a = 3 and b like ‘%kk’ and c = 4 Y,只用到 a
where a = 3 and b like ‘%kk%’ and c = 4 Y,只用到 a
where a = 3 and b like ‘k%kk%’ and c = 4 Y,使用到 a,b,c

11. 口诀

全职匹配我最爱, 最左前缀要遵守;
带头大哥不能死, 中间兄弟不能断;
索引列上少计算, 范围之后全失效;
LIKE 百分写最右, 覆盖索引不写*;
不等空值还有 OR, 索引影响要注意;
VAR 引号不可丢, SQL 优化有诀窍。

第 9 章 关联查询优化

1. 建表语句

CREATE TABLE IF NOT EXISTS `class` (
    `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    `card` INT(10) UNSIGNED NOT NULL,
    PRIMARY KEY (`id`)
);
CREATE TABLE IF NOT EXISTS `book` (
    `bookid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    `card` INT(10) UNSIGNED NOT NULL,
    PRIMARY KEY (`bookid`)
);

INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

2. 案例

2.1 left join

mysql> EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: class
   partitions: NULL
         type: ALL -- 这里
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 20
     filtered: 100.00
        Extra: NULL
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: book
   partitions: NULL
         type: ALL -- 这里
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 20
     filtered: 100.00
        Extra: Using where; Using join buffer (Block Nested Loop)
2 rows in set, 1 warning (0.00 sec)

②给非驱动表加索引,好

如何优化? 在哪个表上建立索引?在book表加索引

ALTER TABLE book ADD INDEX idx_card( card);

mysql> EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: class
   partitions: NULL
         type: ALL -- 
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 20
     filtered: 100.00
        Extra: NULL
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: book
   partitions: NULL
         type: ref -- 这里变为ref了
possible_keys: idx_card
          key: idx_card
      key_len: 4
          ref: test.class.card
         rows: 1
     filtered: 100.00
        Extra: Using index
2 rows in set, 1 warning (0.00 sec)

③给驱动表加索引,不好

删除 book 表的索引: drop index idx_card on book;

在 class 表上建立索引: alter table class add index idx_card(card);

mysql> EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: class
   partitions: NULL
         type: index -- 这里
possible_keys: NULL
          key: idx_card
      key_len: 4
          ref: NULL
         rows: 20
     filtered: 100.00
        Extra: Using index
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: book
   partitions: NULL
         type: ALL -- 
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 20
     filtered: 100.00
        Extra: Using where; Using join buffer (Block Nested Loop)
2 rows in set, 1 warning (0.00 sec)

结论:

①在优化关联查询时, 只有在被驱动表上建立索引才有效!
left join 时, 左侧的为驱动表, 右侧为被驱动表!(即左连接要把索引放到右表上)

2.2 inner join

①EXPLAIN SELECT * FROM book inner join class on class.card=book.card;

mysql> EXPLAIN SELECT * FROM book inner join class on class.card=book.card\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: book
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 20
     filtered: 100.00
        Extra: NULL
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: class
   partitions: NULL
         type: ref
possible_keys: idx_card
          key: idx_card
      key_len: 4
          ref: test.book.card
         rows: 1
     filtered: 100.00
        Extra: Using index
2 rows in set, 1 warning (0.00 sec)

②两个查询字段调换顺序, 发现结果也是一样的!

mysql> EXPLAIN SELECT * FROM class inner join book on class.card=book.card\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: book
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 20
     filtered: 100.00
        Extra: NULL
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: class
   partitions: NULL
         type: ref
possible_keys: idx_card
          key: idx_card
      key_len: 4
          ref: test.book.card
         rows: 1
     filtered: 100.00
        Extra: Using index
2 rows in set, 1 warning (0.00 sec)

③在 book 表中, 删除 9 条记录

delete from book where bookid<10;
select count(*) from book;
mysql> select count(*) from book;
+----------+
| count(*) |
+----------+
|       11 |
+----------+
1 row in set (0.00 sec)
此时book表的数据比class表的数据少了
explain select * from class inner join book on class.card=book.card \G

mysql> explain select * from class inner join book on class.card=book.card \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: book
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 11
     filtered: 100.00
        Extra: NULL
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: class
   partitions: NULL
         type: ref
possible_keys: idx_card
          key: idx_card
      key_len: 4
          ref: test.book.card
         rows: 1
     filtered: 100.00
        Extra: Using index
2 rows in set, 1 warning (0.00 sec)

④结论: inner join 时, mysql 会自己帮你把小结果集的表选为驱动表
⑤straight_join: 效果和 inner join 一样, 但是会强制将左侧作为驱动表!

explain select * from class straight_join book on class.card=book.card \G

mysql> explain select * from class straight_join book on class.card=book.card \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: class
   partitions: NULL
         type: index
possible_keys: idx_card
          key: idx_card
      key_len: 4
          ref: NULL
         rows: 20
     filtered: 100.00
        Extra: Using index
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: book
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 11
     filtered: 10.00
        Extra: Using where; Using join buffer (Block Nested Loop)
2 rows in set, 1 warning (0.00 sec)

2.3 四个关联查询案例分析

mysql> EXPLAIN SELECT ed.name '人物',c.name '掌门' FROM (SELECT e.name,d.ceo from t_emp e LEFT JOIN t_dept d on e.deptid=d.id) ed LEFT JOIN t_emp c on ed.ceo= c.id \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: e
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 10
     filtered: 100.00
        Extra: NULL
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: d
   partitions: NULL
         type: eq_ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: test.e.deptId
         rows: 1
     filtered: 100.00
        Extra: NULL
*************************** 3. row ***************************
           id: 1
  select_type: SIMPLE
        table: c
   partitions: NULL
         type: eq_ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: test.d.CEO
         rows: 1
     filtered: 100.00
        Extra: NULL
3 rows in set, 1 warning (0.00 sec)

分别是ALL,EQ_REF ,ALL,EQ_REF

mysql> EXPLAIN SELECT e.name '人物',tmp.name '掌门' FROM t_emp e LEFT JOIN (SELECT d.id did,e.name FROM t_dept d LEFT JOIN t_emp e ON d.ceo=e.id)tmp ON e.deptId=tmp.did \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: e
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 10
     filtered: 100.00
        Extra: NULL
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: d
   partitions: NULL
         type: eq_ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: test.e.deptId
         rows: 1
     filtered: 100.00
        Extra: NULL
*************************** 3. row ***************************
           id: 1
  select_type: SIMPLE
        table: e
   partitions: NULL
         type: eq_ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: test.d.CEO
         rows: 1
     filtered: 100.00
        Extra: NULL
3 rows in set, 1 warning (0.00 sec)

分别是ALL,ALL,ALL,EQ_REF

上述两个案例, 第一个查询效率较高, 且有优化的余地。 第二个案例中, 子查询作为 被驱动表, 由于子查询是虚表,无法建立索引, 因此不能优化。

结论:

子查询尽量不要放在 被驱动表, 有可能使用不到索引;
left join时, 尽量让实体表作为被驱动表

mysql> EXPLAIN SELECT e1.name '人物',e2.name '掌门' FROM t_emp e1
    -> LEFT JOIN t_dept d on e1.deptid = d.id
    -> LEFT JOIN t_emp e2 on d.ceo = e2.id \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: e1
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 10
     filtered: 100.00
        Extra: NULL
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: d
   partitions: NULL
         type: eq_ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: test.e1.deptId
         rows: 1
     filtered: 100.00
        Extra: NULL
*************************** 3. row ***************************
           id: 1
  select_type: SIMPLE
        table: e2
   partitions: NULL
         type: eq_ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: test.d.CEO
         rows: 1
     filtered: 100.00
        Extra: NULL
3 rows in set, 1 warning (0.00 sec)
mysql> Explain SELECT e2.name '人物',
    -> (SELECT e1.name FROM t_emp e1 where e1.id= d.ceo) '掌门'
    -> from t_emp e2 LEFT JOIN t_dept d on e2.deptid=d.id \G
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: e2
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 10
     filtered: 100.00
        Extra: NULL
*************************** 2. row ***************************
           id: 1
  select_type: PRIMARY
        table: d
   partitions: NULL
         type: eq_ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: test.e2.deptId
         rows: 1
     filtered: 100.00
        Extra: NULL
*************************** 3. row ***************************
           id: 2
  select_type: DEPENDENT SUBQUERY
        table: e1
   partitions: NULL
         type: eq_ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: test.d.CEO
         rows: 1
     filtered: 100.00
        Extra: NULL
3 rows in set, 2 warnings (0.00 sec)

结论: 能够直接多表关联的尽量直接关联, 不用子查询!

第 10 章 子查询优化

1. 案例

取所有不为掌门人的员工, 按年龄分组!

mysql> EXPLAIN select age as '年龄', count(*) as '人数' from t_emp where id not in  (select ceo from t_dept where ceo is not null) group by age \G
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: t_emp
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 10
     filtered: 100.00
        Extra: Using where; Using temporary; Using filesort
*************************** 2. row ***************************
           id: 2
  select_type: SUBQUERY
        table: t_dept
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 6
     filtered: 83.33
        Extra: Using where
2 rows in set, 1 warning (0.00 sec)

如何优化?

①解决 dept 表的全表扫描, 建立 ceo 字段的索引:

create index idx_ceo on t_dept(ceo);

mysql> EXPLAIN select age as '年龄', count(*) as '人数' from t_emp where id not in  (select ceo from t_dept where ceo is not null) group by age \G
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: t_emp
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 10
     filtered: 100.00
        Extra: Using where; Using temporary; Using filesort
*************************** 2. row ***************************
           id: 2
  select_type: SUBQUERY
        table: t_dept
   partitions: NULL
         type: range
possible_keys: idx_ceo
          key: idx_ceo
      key_len: 5
          ref: NULL
         rows: 5
     filtered: 100.00
        Extra: Using where; Using index
2 rows in set, 1 warning (0.00 sec)

此时, 再次查询:

②进一步优化, 替换 not in。
上述 SQL 可以替换为:

select age as '年龄',count(*) as '人数' from emp e left join dept d on e.id=d.ceo where d.id is null group by age;

结论: 在范围判断时, 尽量不要使用 not in 和 not exists, 使用 left join on xxx is null 代替。

第 11 章 排序order by分组group by优化

where 条件和 on 的判断这些过滤条件, 作为优先优化的部门, 是要被先考虑的! 其次, 如果有分组和排序, 那么也要考虑 group by 和 order by

尽量使用index方式排序,避免使用fileSort方式排序

1. 无过滤不索引

create index idx_age_deptid_name on emp (age,deptid,name);
------------------------------------
explain select * from emp where age=40 order by deptid;
显示using where

explain select * from emp order by age,deptid;
显示using filesort

explain select * from emp order by age,deptid limit 10;
没有额外信息

using filesort 说明进行了手工排序! 原因在于没有 where 作为过滤条件!

结论: 无过滤, 不索引。 where, limit 都相当于一种过滤条件, 所以才能使用上索引!

2、顺序错, 必排序

①explain select * from emp where age=45 order by deptid,name;

显示using where

②explain select * from emp where age=45 order by deptid,empno;  

显示using where;using  filesort

empno 字段并没有建立索引, 因此也无法用到索引, 此字段需要排序!



③explain select * from emp where age=45 order by name,deptid;  

显示using where;using  filesort

where 两侧列的顺序可以变换, 效果相同, 但是 order by 列的顺序不能随便变换!



④explain select * from emp where deptid=45 order by age;  

显示using where;using  filesort

deptid 作为过滤条件的字段, 无法使用索引, 因此排序没法用上索引

3、方向反,必排序

①explain select * from emp where age=45 order by deptid desc, name desc ;

显示using where

如果可以用上索引的字段都使用正序或者逆序, 实际上是没有任何影响的, 无非将结果集调换顺序。
②explain select * from emp where age=45 order by deptid asc, name desc ;

显示using where;using filesort

如果排序的字段,顺序有差异, 就需要将差异的部分, 进行一次倒置顺序, 因此还是需要手动排序的!

4、索引的选择

①首先, 清除 emp 上面的所有索引, 只保留主键索引!

drop index idx_age_deptid_name on emp;

②查询: 年龄为 30 岁的, 且员工编号小于 101000 的用户, 按用户名称排序

explain SELECT SQL_NO_CACHE * FROM emp WHERE age =30 AND empno <101000 ORDER BY NAME ;

显示using where,using filesort

③全表扫描肯定是不被允许的, 因此我们要考虑优化。
思路: 首先需要让 where 的过滤条件, 用上索引;
查询中, age.empno 是查询的过滤条件, 而 name 则是排序的字段, 因此我们来创建一个此三个字段的复合索引:
create index idx_age_empno_name on emp(age,empno,name);

再次查询, 发现 using filesort 依然存在。
原因: empno 是范围查询, 因此导致了索引失效, 所以 name 字段无法使用索引排序。
所以, 三个字段的符合索引, 没有意义, 因为 empno 和 name 字段只能选择其一!

④解决: 鱼与熊掌不可兼得, 因此, 要么选择 empno,要么选择 name

drop index idx_age_empno_name on emp;
create index idx_age_name on emp(age,name);
create index idx_age_empno on emp(age,empno);  

explain SELECT SQL_NO_CACHE * FROM emp WHERE age =30 AND empno <101000 ORDER BY NAME ;  

两个索引同时存在, mysql 会选择哪个?

显示key使用的是age_name

explain SELECT SQL_NO_CACHE * FROM emp use index(idx_age_name) WHERE age =30 AND empno <101000 ORDER BY name;
显示using where,使用的是key是age——name

原因: 所有的排序都是在条件过滤之后才执行的, 所以如果条件过滤了大部分数据的话, 几百几千条数据进行排序其实并不是很消耗性能, 即使索引优化了排序但实际提升性能很有限。 相对的 empno<101000 这个条件如果没有用到索引的话, 要对几万条的数据进行扫描, 这是非常消耗性能的, 使用 empno 字段的范围查询, 过滤性更好(empno 从 100000 开始)!

结论: 当范围条件和 group by 或者 order by 的字段出现二选一时 , 优先观察条件字段的过滤数量, 如果过滤的数据足够多, 而需要排序的数据并不多时, 优先把索引放在范围字段上。 反之, 亦然。

5、using filesort

5.1 mysql 的排序算法

①双路排序
MySQL 4.1 之前是使用双路排序,字面意思就是两次扫描磁盘, 最终得到数据, 读取行指针和 order by 列, 对他们进行排序, 然后扫描已经排序好的列表, 按照列表中的值重新从列表中读取对应的数据输出。
从磁盘取排序字段, 在 buffer 进行排序, 再从磁盘取其他字段。
简单来说, 取一批数据, 要对磁盘进行了两次扫描, 众所周知, I\O 是很耗时的, 所以在mysql4.1 之后, 出现了第二种改进的算法, 就是单路排序。
②单路排序
从磁盘读取查询需要的所有列, 按照 order by 列在 buffer 对它们进行排序, 然后扫描排序后的列表进行输出,
它的效率更快一些, 避免了第二次读取数据。 并且把随机 IO 变成了顺序 IO,但是它会使用更多的空间,
因为它把每一行都保存在内存中了。
③单路排序的问题
由于单路是后出的, 总体而言好过双路。 但是存在以下问题:
在 sort_buffer 中, 方法 B 比方法 A 要多占用很多空间, 因为方法 B 是把所有字段都取出, 所以有可能取出的数据的总大小超出了 sort_buffer 的容量, 导致每次只能取 sort_buffer 容量大小的数据, 进行排序(创建 tmp 文件, 多路合并), 排完再取取 sort_buffer 容量大小, 再排……从而多次 I/O。
结论: 本来想省一次 I/O 操作, 反而导致了大量的 I/O 操作, 反而得不偿失。
5.2 如何优化

①增大 sort_butter_size 参数的设置
不管用哪种算法, 提高这个参数都会提高效率, 当然, 要根据系统的能力去提高, 因为这个参数是针对每个进
程的 1M-8M 之间调整。
②增大 max_length_for_sort_data 参数的设置
mysql 使用单路排序的前提是排序的字段大小要小于 max_length_for_sort_data。
提高这个参数, 会增加用改进算法的概率。 但是如果设的太高, 数据总容量超出 sort_buffer_size 的概率就增大,
明显症状是高的磁盘 I/O 活动和低的处理器使用率。(1024-8192 之间调整)。
③减少 select 后面的查询的字段。
当 Query 的字段大小总和小于 max_length_for_sort_data 而且排序字段不是 TEXT|BLOB 类型时, 会用改进后的算法——单路排序, 否则用老算法——多路排序。
两种算法的数据都有可能超出 sort_buffer 的容量, 超出之后, 会创建 tmp 文件进行合并排序, 导致多次 I/O,但是用单路排序算法的风险会更大一些,所以要提高 sort_buffer_size。

6. 使用覆盖索引

覆盖索引: SQL 只需要通过索引就可以返回查询所需要的数据, 而不必通过二级索引查到主键之后再去查询数据

explain select * from emp where age<> 40;
显示using where

explain select id,name from emp where age<>40;
显示usingwhere,using index

7、group by

group by 使用索引的原则几乎跟 order by 一致 , 唯一区别是 groupby 即使没有过滤条件用到索引, 也可以直接使用索引

第12章 练习

第 13 章 截取查询分析

1. 慢查询日志

1.1 是什么

(1) MySQL的慢查询日志是MySQL提供的一种日志记录, 它用来记录在MySQL中响应时间超过阀值的语句, 具体指运行时间超过long_query_time值的SQL, 则会被记录到慢查询日志中。
(2) 具体指运行时间超过long_query_time值的SQL, 则会被记录到慢查询日志中。 long_query_time的默认值为
10, 意思是运行10秒以上的语句。
(3) 由他来查看哪些SQL超出了我们的最大忍耐时间值, 比如一条sql执行超过5秒钟, 我们就算慢SQL, 希望能收集超过5秒的sql, 结合之前explain进行全面分析。

1.2 怎么用

默认情况下, MySQL 数据库没有开启慢查询日志, 需要我们手动来设置这个参数。
当然, 如果不是调优需要的话, 一般不建议启动该参数, 因为开启慢查询日志会或多或少带来一定的性能影响。
慢查询日志支持将日志记录写入文件。
(1) 开启设置

SQL 语句 描述 备注
SHOW VARIABLES LIKE ‘%slow_query_log%’; 查看慢查询日志是否开启 默认情况下 slow_query_log 的值为 OFF, 表示慢查询日志是禁用的
set global slow_query_log=1; 开启慢查询日志
SHOW VARIABLES LIKE ‘long_query_time%’; 查看慢查询设定阈值 单位秒
set long_query_time=1 设定慢查询阈值 单位秒

(2) 如永久生效需要修改配置文件 my.cnf 中[mysqld]下配置

[mysqld]
slow_query_log=1
slow_query_log_file=/var/lib/mysql/atguigu-slow.log
long_query_time=3
log_output=FILE

(3) 运行查询时间长的 sql, 打开慢查询日志查看

1.2 日志分析工具 mysqldumpslow

(1) 查看mysqldumpslow的帮助信息

命令:

[root@cocoon ~]# mysqldumpslow --help  
参数 描述 备注
-s 是表示按照何种方式排序
c 访问次数
l 锁定时间
r 返回记录
t 查询时间
al 平均锁定时间
ar 平均返回记录数
at 平均查询时间
-t 即为返回前面多少条的数据
-g 后边搭配一个正则匹配模式, 大小写不敏感的

( 2) 查看mysqldumpslow的帮助信息

# 得到返回记录集最多的 10 个 SQL
mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu-slow.log

# 得到访问次数最多的 10 个 SQL
mysqldumpslow -s c -t 10 /var/lib/mysql/atguigu-slow.log

# 得到按照时间排序的前 10 条里面含有左连接的查询语句
mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/atguigu-slow.log

# 另外建议在使用这些命令时结合 | 和 more 使用 , 否则有可能出现爆屏情况
mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu-slow.log | more

\2. SHOW PROCESSLIST

1.1 是什么
查询 mysql 进程列表, 可以杀掉故障进程
1.2 怎么用

SHOW PROCESSLIST;
KILL 6;

第 14 章 工具和技巧拾遗

1.1 是什么

将一段查询 sql 封装为一个虚拟的表。
这个虚拟表只保存了 sql 逻辑, 不会保存任何查询结果

1.2 作用

(1) 封装复杂 sql 语句, 提高复用性
(2) 逻辑放在数据库上面, 更新不需要发布程序, 面对频繁的需求变更更灵活

第 15 章 主从复制

1.1 复制的基本原理

(1) slave 会从 master 读取 binlog 来进行数据同步
(2) 三步骤+原理图

MySQL 复制过程分成三步:
master 将改变记录到二进制日志(binary log)。 这些记录过程叫做二进制日志事件, binary log events;
slave 将 master 的 binary log events 拷贝到它的中继日志(relay log);

slave 重做中继日志中的事件, 将改变应用到自己的数据库中。 MySQL 复制是异步的且串行化的
1.2 复制的基本原则
(1) 每个 slave 只有一个 master
(2) 每个 slave 只能有一个唯一的服务器 ID
(3) 每个 master 可以有多个 salve
1.3 复制的最大问题
因为发生多次 IO, 存在延时问题
1.4 一主一从常见配置
(1) mysql 版本一致且后台以服务运行
(2) 主从都配置在[mysqld]结点下, 都是小写
主机修改 my.ini 配置文件

主服务器唯一 ID
server-id=1
启用二进制日志
log-bin=自己本地的路径/data/mysqlbin
log-bin=D:/devSoft/MySQLServer5.5/data/mysqlbin
设置不要复制的数据库
binlog-ignore-db=mysql
设置需要复制的数据库
binlog-do-db=需要复制的主数据库名字
设置 logbin 格式
binlog_format=STATEMENT(默认)

mysql 主从复制起始时, 从机不继承主机数据

3) logbin 格式

binlog_format=STATEMENT(默认)
binlog_format=ROW
binlog_format=MIXED

(4) 从机配置文件修改 my.cnf 的[mysqld]栏位下

#从机服务 id
server-id = 2
#注意 my.cnf 中有 server-id = 1
#设置中继日志
relay-log=mysql-relay

(5) 因修改过配置文件, 请主机+从机都重启后台 mysql 服务
(6) 主机从机都关闭防火墙、 安全工具(腾讯管家等)
(7) 在 Windows 主机上建立帐户并授权 slave

#创建用户, 并授权
GRANT REPLICATION SLAVE ON *.* TO '备份账号'@'从机器数据库 IP' IDENTIFIED BY '123456';  

(5) 因修改过配置文件, 请主机+从机都重启后台 mysql 服务
(6) 主机从机都关闭防火墙、 安全工具(腾讯管家等)
(7) 在 Windows 主机上建立帐户并授权 slave
#创建用户, 并授权
GRANT REPLICATION SLAVE ON . TO ‘备份账号’@‘从机器数据库 IP’ IDENTIFIED BY ‘123456’;

(8) 查询 master 的状态, 并记录下 File 和 Position 的值

\#查询 master 的状态
show master status;  

执行完此步骤后不要再操作主服务器 mysql, 防止主服务器状态值变化
(9) 在 Linux 从机上配置需要复制的主机

\#查询 master 的状态
CHANGE MASTER TO MASTER_HOST='主机 IP',MASTER_USER='创建用户名',MASTER_PASSWORD='创建的密码',
MASTER_LOG_FILE='File 名字',MASTER_LOG_POS=Position 数字;  

(10) 启动从服务器复制功能
start slave;
show slave status\G;

下面两个参数都是 Yes, 则说明主从配置成功!
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
(11) 主机新建库、 新建表、 insert 记录, 从机复制
(12) 如何停止从服务复制功能
stop slave;

第 16 章 MYCAT

1.1 是什么

数据库中间件,前身是阿里的 cobar

1.2 做什么

(1) 读写分离
(2) 数据分片:
垂直拆分、 水平拆分
垂直+水平拆分

(3) 多数据源整合

1.3 MYCAT 原理
“拦截”: Mycat 的原理中最重要的一个动词是“拦截”, 它拦截了用户发送过来的 SQL 语句, 首先对 SQL 语
句做了一些特定的分析: 如分片分析、 路由分析、 读写分离分析、 缓存分析等, 然后将此 SQL 发往后端的真实数
据库, 并将返回的结果做适当的处理, 最终再返回给用户

这种方式把数据库的分布式从代码中解耦出来, 程序员察觉不出来后台使用 mycat 还是 mysql。
1.4 安装启动
(1) 解压缩文件拷贝到 linux 下 /usr/local/
(2) 三个配置文件
schema.xml 定义逻辑库, 表、 分片节点等内容
rule.xml 定义分片规则
server.xml 定义用户以及系统相关变量, 如端口等.
(3) 启动前先修改 schema.xml

<?xml version="1.0"?>  
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<!--逻辑库 name 名称, checkSQLschema sqlMaxLimit 末尾是否要加 limit xxx-->
<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"> </schema>
<!--逻辑库 name 名称, dataHost 引用的哪个 dataHost database:对应 mysql 的 database-->
<dataNode name="dn1" dataHost="localhost1" database="db1" />
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="native" switchType="1"
slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<!-- can have multi write hosts -->
<writeHost host="hostM1" url="localhost:3306" user="root"
password="123456">
</writeHost>
</dataHost>
</mycat:schema>

(4) 再修改 server.xml

<user name="root">
<property name="password">654321</property>
<property name="schemas">TESTDB</property>
</user>
控制台启动 : 去 mycat/bin 目录下 mycat console
后台启动 : 去 mycat/bin 目录下 mycat start 

(5) 启动程序
控制台启动 : 去 mycat/bin 目录下 mycat console
后台启动 : 去 mycat/bin 目录下 mycat start

(5) 启动程序

(6) 启动时可能出现报错
域名解析失败

用 vim 修改 /etc/hosts 文件,在 127.0.0.1 后面增加你的机器名

修改后重新启动网络服务

service network restart

7) 后台管理窗口登录

常见问题

插入万条数据

插入万条数据很慢怎么办?用for格式一条一条插入很慢

<?php
    $connect=@mysql_connect("localhost","root","") or die(mysql_error());
@mysql_select_db("test") or die(mysql_error());
@mysql_query("set names utf-8");
$start_time=microtime(true);
/*
 * 方法一,循环插入mysql数据
 */
$sql="insert into test_table(value) values('1')";
for($i=0;$i<100000;$i++)
{
    
    
    @mysql_query($sql) or die(mysql_error());
}

$end_time=microtime(true);

echo "程序的运行时间为:".($end_time-$start_time);

改善:即使用一条sql语句,对数据进行插入处理。

insert into table value(value1,value2.......)

猜你喜欢

转载自blog.csdn.net/hancoder/article/details/105773095
今日推荐