一、表属性设置为NULL,你可能要面临很多麻烦
1.为什么有很多人会用null呢?
a.NULL是默认行为,如果你不指定列字是NOT NULL的,那么他就是NULL
b.一个很严重的误区:NULL不占用存储空间(是一种优化行为)
c.NULL属性非常方便,SQL语句或者代码不需要额外的填充或判断
我们似乎听说过NULL 列并不好,到底该听谁的呢?不能仅凭听说,官网的NULL列的描述
NULL columns require additional space in the rowto record whether their values areNULL.For MyISAM tables, each NULL columntakes one bit extra,rounded up to thenearest byte.
翻译:MYSQL难以优化引用可空列查询,它会使索引、索引统计和值更加复杂。可空列需要更多的存储空间,还需要MYSQL内部进行特殊处理。可空列被索引后,每条记录都需要一条额外的字节,还能导致MYISAM中固定的大小索引变成可变大小的索引
2.表属性设置为NULL,你可能要面临很多问题
a.NULL列存在的问题/容易引起BUG的特性(NULL列需要你关注的地方)
b.NULL的长度并不是0
c.NULL参与的查询
d.NULL对索引的影响
e.NULL参与计算
f.NULL参与聚合
g.NULL参与排序
我们一直说NULL是占用空间的,接下来我们通过调用length函数看一看,
a.空字符串占用的空间()
b.NULL占用的内存空间
c.0 Int类型占据的空间
d.字符串0占据的空间
select length(‘’),length(null),length(0),length(‘0’)
3.实例
CREATE TABLE `do_not_use_null` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`one` varchar(10) NOT NULL,
`two` varchar(20) DEFAULT NULL,
`three` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_one` (`one`),
KEY `idx_two` (`two`),
UNIQUE KEY `idx_three` (`three`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `do_not_use_null`(`id`, `one`, `two`, `three`) VALUES (1, '', 'a2', 'a3');
INSERT INTO `do_not_use_null`(`id`, `one`, `two`, `three`) VALUES (2, 'b1', NULL, 'b3');
INSERT INTO `do_not_use_null`(`id`, `one`, `two`, `three`) VALUES (3, 'c1', 'c2', NULL);
INSERT INTO `do_not_use_null`(`id`, `one`, `two`, `three`) VALUES (4, 'd1', 'd2', NULL);
a.第一个问题
如果是NULL,MYSQL会显示NULL
b.第二个问题
select * from do_not_use_null where two = null;
select * from do_not_use_null where two != null; null的一个坑
select * from do_not_use_null where two is null或is not null
select * from do_not_use_null where two != 'xyz'
select * from do_not_use_null where two != 'xyz' and two is not null
c.第三个问题
表结构字段two加索引,为NULL的字段是允许加索引的,我们要关注的是能不能用索引
desc select * from do_not_use_null where two != 'xyz' 可以看到没有使用索引
desc select * from do_not_use_null where two is null或is not null 可以看到是可以使用索引的,这就是null对索引的坑
大家继续看,three字段我加了唯一性索引,但第三条第四条重复了,唯一性索引失效了,可以看到允许插入两条或多条为NULL的记录
d.四个问题
允许为字段为NULL,字段的数据类型从程序来说字段的类型是不统一的,NULL值与其它数字类型计算返回的都是NULL
select 1+2--->结果是3
select 1+NULL--->结果是NULL
select CONCAT('abc',null)--->结果也是NULL
大家可以看出这就是允许NULL列造成数据不统一的结果,MYSQL就规定参与计算返回的结果是null
e.第五个问题计算表中有多少条记录
select count(*) from do_not_use_null--->4
select count(two) from do_not_use_null--->3
select count(three)from do_not_use_null--->2
f.第六个我问题
select * from do_not_use_null order by two asc--->可以看出NULL在前面
select * from do_not_use_null order by two desc--->可以看出NULL在后面
那怎么解决呢,我应该用什么去代替NULL呢?
使用特殊值去填充null:空字符串(字符串类型)或数字0(数字类型)
对于已经存在数据的表,填充特殊值到null列,再去修改表结构(列限制)
注意:任何场景下你都不应该考虑使用NULL
总结:初始化一些数据,NULL的长度是NULL而不是0,使用等于或不等于NULL都查不到要使用IS NULL或IS NOT NULL,等于、不等于某字段都会默认排除NULL的,NULL不参与计算,有NULL要么在开头要么在末尾
二、不再随意设置数据类型,不给未来留隐患
1.不随意设置数据类型
a.MySQL允许建表时不指定主键,但是一定要指定一个主键
如果没有指定主键—>是否存在非空整形唯一索引(成为主键)—>Innodb自动添加隐式索引
b.主键不具有任何的业务含义,只是一个唯一的自增整数值
如果是有业务含义列入字符串(随机的主键),频繁列变导致数据页填充不理想,占用磁盘空间;对于随机的主键
所有数据页被随机写入,从而导致无法搞笑的缓存数据页
c.选择合适的数据类型以及恰当的取值范围
d.不要使用枚举类型
2.选择合适的数据类型及恰当的范围
MYSQL中定义了四类数据类型,且有不同的取值范围
类型名称 | 类型 |
字符串 | char、varchar、【tinytext、text、mediumtext、longtext】 |
日期/时间 | date、time、datetime、timestamp |
数值 | tinyint、int、bigint、float、double、decimal |
二进制 | tityblob、blob、mediumblob、longblob |
注:
1.对于char,不足用空格填充,浪费,varchar,用多少占用多少
2.存储数据量大考虑文本,超过500建议使用文本,不能全部成为索引,需指定前多少位
3.日期、时间、日期时间组合、时间戳比前者存储的数据范围小一些
4.tinyint、int、bigint属于整数类型(取值范围不同),float、double属于浮点类型(精度),decimal是定点数据类型(对精确值的要求,对精确要求非常高)
4.二进制使用率低,在于范围的不同,降低数据库性能,能不使用就不使用
3.枚举
CREATE TABLE `suitable_data_type` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL,
`gender` ENUM('male', 'female') NOT NULL,
`grade` ENUM('0', '1', '2') NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
3.1、插入一条正确的数据(枚举值都是自己定义的)
INSERT INTO suitable_data_type
(id
, name
, gender
, grade
) VALUES (1, ‘wcl’, ‘male’, ‘1’);
3.2、插入一条错误的数据, 会执行失败
INSERT INTO suitable_data_type
(id
, name
, gender
, grade
) VALUES (2, ‘wcl’, ‘male’, ‘9’);
3.3、下面几条语句插入的数据是什么 ? (ENUM 字面量的处理)
INSERT INTO `suitable_data_type`(`id`, `name`, `gender`, `grade`) VALUES (3, 'wcl', 'male', 2);--->可以看到插入的值为1,翻译成了索引
INSERT INTO `suitable_data_type`(`id`, `name`, `gender`, `grade`) VALUES (4, 'wcl', 'male', '2');
INSERT INTO `suitable_data_type`(`id`, `name`, `gender`, `grade`) VALUES (5, 'wcl', 'male', '3');--->可以看到 插入的值为2,以索引形式匹配到了列表
INSERT INTO `suitable_data_type`(`id`, `name`, `gender`, `grade`) VALUES (6, 'wcl', 'male', '4');
3.4、查看枚举值对应的索引(通过gender + 0表示mysql知道我们要查枚举的索引)
select name, gender + 0, grade from suitable_data_type; – +0查询枚举的索引,索引从1开始
3.5、聚合函数
select sum(gender) from suitable_data_type;--->看到值为4,索引相加
select avg(gender) from suitable_data_type;
3.6、枚举对排序的影响
INSERT INTO suitable_data_type
(id
, name
, gender
, grade
) VALUES (7, ‘abc’, ‘female’, ‘0’);
select * from suitable_data_type order by gender;
select * from suitable_data_type order by cast(gender as char);--->按照字符排序
select * from suitable_data_type order by concat(gender);--->将多个字段拼接成一个字符串
三、索引加的不好,可能适得其反
1.索引加的正确,但是没有写出适用索引的查询语句
a.字符串类型在查询时没有使用引号,不会使用表索引
b.where条件左边的(属性列)字段参与了函数或者数学运算,不会使用表索
where func/math left op right
c.联合索引最左前缀顺序不匹配,不会使用表索引
2.举例
CREATE TABLE `correct_use_index` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL,
`age` int(11) NOT NULL,
`phone` varchar(64) NOT NULL,
`email` varchar(128) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_phone` (`phone`),
KEY `idx_name_phone_email` (`name`, `phone`, `email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2.1、查看表的索引
show index from correct_use_index;
2.2、字符串类型查询没有使用引号(手机号码)
explain select * from correct_use_index where phone = 17012345678;
explain select * from correct_use_index where phone = ‘17012345678’;
2.3、where 条件左边的字段参与了函数或者数学运算
explain select * from correct_use_index where concat(name, ‘-wcl’) = ‘bd-wcl’
上面我们是加了索引的,可以看出使用函数,MYSQL放弃了索引
explain select * from correct_use_index where name = ‘bd’;
2.4、其他的例子(看一下数学运算)
explain select * from correct_use_index where age - 10 > 0;—> 对属性做了运算
explain select * from correct_use_index where age > 10;—>如果有索引就能用索引了
2.5、联合索引的前缀使用问题 (where条件的运行是从右到左的,将选择性强的条件放到最右边,可以先过滤掉大部分的数据(而选择性不强的条件过滤后的结果集仍然很大),在根据其它条件过滤时,需要比较的数据量就少,提高查询的效率。)
drop index idx_phone ON correct_use_index;(先把这个索引删除,以防止联合索引在查询时优化器会用到这个索引)
explain select * from correct_use_index where name = ‘wcl’ and phone = ‘10086’ and email = ‘wcl@bd.com’;
explain select * from correct_use_index where phone = ‘10086’ and email = ‘wcl@bd.com’;
3.索引加的不正确、冗余
a.不再使用的索引没有及时删除:空间浪费、插入、删除更新性能受影响,MYSQL维护索引也需要消耗资源
b.索引选择性太低,索引列的意义不大(索引选择性=不重复的索引值/表记录数)
c.列值过长,可以选择部分前缀作为索引(区分高度的情况下)而不是阵列加上索引
d.表记录比较少,全表扫描效率更高(这是相对的),我的个人经验是一千行为界限
e.存在联合索引的情况下,再去对前缀部分加索引(已经覆盖了单例或多列索引),是没有意义的
f.一张表建立索引过多(超过五个)应该是根据业务需求去创建索引,并不是越多越好,太多会浪费空间,影响额外效率
4.举例
CREATE TABLE `correct_use_index_2` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`career` varchar(32) NOT NULL,
`first_name` varchar(16) NOT NULL,
`last_name` varchar(16) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `correct_use_index_2`(`id`, `career`, `first_name`, `last_name`) VALUES (1, 'engineer', 'wcl', 'abcdefg');
INSERT INTO `correct_use_index_2`(`id`, `career`, `first_name`, `last_name`) VALUES (2, 'engineer', 'wcl', 'abxyzbdf');
INSERT INTO `correct_use_index_2`(`id`, `career`, `first_name`, `last_name`) VALUES (3, 'engineer', 'wcl', 'aerefgdgfd');
INSERT INTO `correct_use_index_2`(`id`, `career`, `first_name`, `last_name`) VALUES (4, 'engineer', 'wcl', 'abpoijhyg');
INSERT INTO `correct_use_index_2`(`id`, `career`, `first_name`, `last_name`) VALUES (5, 'engineer', 'wcl', 'acqasdwqer');
4.1、索引选择性(没有必要为索引选择性较低的列创建索引)
select count(distinct(career))/count(*) from correct_use_index_2; —>越小说明索引选择性低,数据重复率高
4.2、想要通过 name 去查询记录, 可以考虑创建 first_name 索引, 或 first_name、last_name 联合索引 --> 看一看索引选择性
select * from correct_use_index_2 where first_name = ‘’ and last_name = ‘’;
select count(distinct(first_name))/count(*) from correct_use_index_2; —>可以看出值非常低
此时我们考虑创建联合索引
4.3、first_name, last_name 创建联合索引占据的空间会过大, 需要考虑兼顾长度和选择性
select count(distinct(concat(first_name, last_name)))/count(*) from correct_use_index_2;
分别通过1个字符、2个字符、3个字符看索引的选择性
select count(distinct(concat(first_name, left(last_name, 1))))/count(*) from correct_use_index_2;
select count(distinct(concat(first_name, left(last_name, 2))))/count(*) from correct_use_index_2;
select count(distinct(concat(first_name, left(last_name, 3))))/count(*) from correct_use_index_2;
可以看出用firstname和lastname前三个字符构成索引即可
ALTER TABLE correct_use_index_2 ADD INDEX `idx_first_last_name_3` (first_name, last_name(3));
show index from correct_use_index_2;
四、MYSQL为什么莫名其妙断开连接
(一段时间没用就断开了,怎么规避这个问题)
1.查看超时的默认时间
show global variables like ‘%wait_timeout%’ 可以看出是86400秒也就是8小时
2.超时的解决方法
autoReconnet的属性设置为true来避免这个问题
看一下官网对autoReconnet参数的解释
可以看到官网不推荐使用这个参数,因为它有一些副作用:
a.原有连接上的事务将会被回滚,事务的提交模式将会丢失
b.原有连接持有的表的锁将会全部释放
c.原有连接关联的会话Session将会丢失,重新恢复的连接关联的将会是一个新的会话Session
d.原有连接定义的用户变量将会丢失
e.原有连接定义的预编译SQL将会丢失
f.原有连接失效,新的连接恢复后,MySQL将会使用新的记录行来存储连接中的性能数据
3.设置超时时间的方法
a.命令设置
set global wait_timeout=288000;
b.配置文件设置(my.cnf/my.ini)
打开文件,在mysqld下修改
wait_timeout=288000
interactive_timeout=288000 交互式参数
五、事务处理不对,可能是锁用的不对
1.数据库锁的分类
按照数据库锁粒度(等级)去分:行级锁、表级锁
按照数据的锁定方式去分:乐观锁、悲观锁—>排它锁、共享锁
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低
乐观锁:最常见的加版本字段来控制,更新一次对version值加1,经典的CAS,与旧值比对相等设置新值
悲观锁:排他锁、共享锁类似java sync(悲观锁是数据库帮我我们实现,乐观锁是我们自己实现的)
排他锁(X锁):排它锁与共享锁相对应,就是指对于多个不同的事务,对同一个资源只能有一把锁。用于数据修改操作,例如 INSERT、UPDATE 或 DELETE。确保不会同时同一资源进行多重更新。
共享锁(S锁):共享 (S) 指的就是对于多个不同的事务,对同一个资源共享同一个锁. 用于不更改或不更新数据的操作(只读操作),如 SELECT 语句。
2.举例
CREATE TABLE `lock_error_use_in_transaction` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL,
`age` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `lock_error_use_in_transaction`(`id`, `name`, `age`) VALUES (1, 'bd', 10);
INSERT INTO `lock_error_use_in_transaction`(`id`, `name`, `age`) VALUES (2, 'wcl', 19);
2.1、乐观锁
update tableName value = 2,version = version+1 where id =1 and version=1
2.2、验证共享锁读写
set autocommit = false;这一步是必要的,我们要使用使用事务
select * from lock_error_use_in_transaction where id =1 lock in share mode;—>共享锁可以让多个会话读数据
update lock_error_use_in_transaction set age =12 where id = 1;—>没反应,因为共享锁没释放,需要等其他会话释放锁
在另一个会话也执行这条,此时报错了,本来没释放,然后同时写,可以看到两个会话互等释放,一会就报错了,这里报错另一边就可以了
2.2、排它锁
排他锁称为写锁,如果某个事务对某一行被加上了排他锁,那么只能这个事务对他进行读写,其它事务只能只能等待释放才能操作
set autocommit = false;前置条件
select * from lock_error_use_in_transaction where id =1 for update ,
格式select for update
在另一个会话页执行,可以看到卡住了,卡的时间长会出现获取锁超时,会给你停止
通过列子验证说的两点坑
还是之前一样设置
set autocommit = false;前置条件
update lock_error_use_in_transaction set age =11 where name =‘wcl’,自动给你加排他锁
执行一下成功,放到另外一个会话,可以看到卡住,因为排他锁生效,并不是修改同一条
update lock_error_use_in_transaction set age =11 where name =‘aaa’,
就是刚才说的行锁,表锁的问题,没有添加索引造成,可以加索引解决
注意点一:mysql默认的事务隔离级别是REPEATABLE READ(可重复读),假设一个事务中执行了一条update语句,一般有索引的情况下,mysql会将该记录加锁;如果这个事务还没有提交的情况下,另一个事务也对该条记录进行了update操作。那么,第二个事务的update语句需要等待第一个事务提交以后,mysql才会将锁进行释放,进而第二条update语句才会开始执行。
2.3、写的SQL很慢,怎样优化
show variables like '%slow_query_log%';
两种方式设置:改写当前会话的形式;配置文件实现永久的改写
// 开启慢查询
slow_query_log = on
// 设置慢查询时间 1 s
long_query_time=1
// 慢查询日志保存路径
slow_query_log_file="D:/soft/xampp/mysql/logs/slow_query.log"
// 开启 记录没有使用索引查询语句
log-queries-not-using-indexes = on
至少前两项,其余默认
六、数据量增大才考虑分库分表可行吗
1.有一张用户信息表
CREATE TABLE `user` (
`user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`user_name` varchar(128) NOT NULL DEFAULT '' COMMENT '用户名',
`password` varchar(128) NOT NULL DEFAULT '' COMMENT '密码',
`email` varchar(128) NOT NULL DEFAULT '' COMMENT '电子邮箱',
`phone` varchar(128) NOT NULL DEFAULT '' COMMENT '手机号码',
`gender` tinyint(4) NOT NULL DEFAULT '0' COMMENT '性别',
`age` tinyint(4) NOT NULL DEFAULT '0' COMMENT '年龄',
`id_card` varchar(128) NOT NULL DEFAULT '' COMMENT '身份证号码',
`intro` varchar(1024) NOT NULL DEFAULT '' COMMENT '个人信息',
`user_company` varchar(50) NOT NULL DEFAULT '' COMMENT '用户公司',
`user_department` varchar(45) NOT NULL DEFAULT '' COMMENT '用户部门',
`user_duty` varchar(100) NOT NULL DEFAULT '' COMMENT '用户具体职责',
`user_industry` varchar(100) NOT NULL DEFAULT '' COMMENT '用户所处行业',
`user_status` int(10) NOT NULL DEFAULT '0' COMMENT '用户状态',
`create_time` datetime NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT '更新时间',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户信息表';
2.切分成两张表(user表越来越大,影响新增查询效率)
– 用户基本信息表
CREATE TABLE `user_base` (
`user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`user_name` varchar(128) NOT NULL DEFAULT '' COMMENT '用户名',
`password` varchar(128) NOT NULL DEFAULT '' COMMENT '密码',
`email` varchar(128) NOT NULL DEFAULT '' COMMENT '电子邮箱',
`phone` varchar(128) NOT NULL DEFAULT '' COMMENT '手机号码',
`gender` tinyint(4) NOT NULL DEFAULT '0' COMMENT '性别',
`age` tinyint(4) NOT NULL DEFAULT '0' COMMENT '年龄',
`user_status` int(10) NOT NULL DEFAULT '0' COMMENT '用户状态',
`create_time` datetime NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT '更新时间',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户基本信息表';
– 用户附加信息表
CREATE TABLE `user_extra` (
`user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`id_card` varchar(128) NOT NULL DEFAULT '' COMMENT '身份证号码',
`intro` varchar(1024) NOT NULL DEFAULT '' COMMENT '个人信息',
`user_company` varchar(50) NOT NULL DEFAULT '' COMMENT '用户公司',
`user_department` varchar(45) NOT NULL DEFAULT '' COMMENT '用户部门',
`user_industry` varchar(100) NOT NULL DEFAULT '' COMMENT '用户所处行业',
`user_duty` varchar(100) NOT NULL DEFAULT '' COMMENT '用户具体职责',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户附加信息表';
准备两个hash函数
– HASH1:DB 确定水平切分属于哪个库
– HASH2:TABLE 确定水平切分属于哪个表
三到五年预估数据一亿,每个库预计2500万
1亿/2500万 = 4DB
每张表预计200万数据
2500万/500万=5TABLE
HASH1(user_id)% 4 DB 确定库
HASH2(user_id)% 5 TABLE 确定表
七、MYSQL其它坑总结
1.自增id在服务器重启后有可能会出现的问题
假设我们在数据库中使用自增id连续插入两条数据,生成的id分别是1和2,这个时候我把id为2的删除掉,这个时候我再插入一条数据,生成的id为3
同样的场景,插入了两条数据,生成ID为1和2, 如果此时把id为2的删掉,这个时候我先重启mysql,在插入一条数据,生成的id为2,如果使用了历史表,如果使用id处理逻辑,可能会导致错误
因为InnoDB的自增值是记录在内存的,不是记录在数据文件的。重启后,会把 当前最大ID + 1 作为起始值。\4. 2
2.自增值用完后怎么办
无符号 int 的最大值为 4294967295,自增值达到此值后,就不变了,新插入记录时就会报错 Duplicate entry ‘4294967295’ for key ‘PRIMARY’
3.mysql中的编码的坑
mysql中的utf8编码不是标准的utf8编码,有时候可能会出现 Incorrect string value: ‘ð <…’ for column ‘summary’ at row 1 的问题。
mysql并没有从根本上解决了这个问题, 只是后来推出了一个 utf8mb64的编码,相当于标准utf8,并且可以存储表情类数据,推荐使用utf8mb64编码类型
八、执行计划参数讲解
expain出来的信息有10列,分别是id、select_type、table、type、possible_keys、key、key_len、ref、rows、Extra,下面对这些字段出现的可能进行解释:
1.ID(我的理解是SQL执行的顺序的标识,SQL从大到小的执行)
a.ID相同时,执行顺序由上至下
b.如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
c.ID如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行
2.select_type(表示查询中每个select子句的类型)
a. SIMPLE(简单SELECT,不使用UNION或子查询等)
b. PRIMARY(查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY)
c.UNION(UNION中的第二个或后面的SELECT语句)
d. DEPENDENT UNION(UNION中的第二个或后面的SELECT语句,取决于外面的查询)
e. UNION RESULT(UNION的结果)
f. SUBQUERY(子查询中的第一个SELECT)
g. DEPENDENT SUBQUERY(子查询中的第一个SELECT,取决于外面的查询)
h.DERIVED(派生表的SELECT, FROM子句的子查询)
i.UNCACHEABLE SUBQUERY(一个子查询的结果不能被缓存,必须重新评估外链接的第一行)
3.table
显示这一行的数据是关于哪张表的,有时不是真实的表名字,看到的是derivedx(x是个数字,我的理解是第几步执行的结果)
4.type(表示MySQL在表中找到所需行的方式,又称“访问类型”)
常用的类型有: ALL, index, range, ref, eq_ref, const, system, NULL(从左到右,性能从差到好)
ALL:Full Table Scan, MySQL将遍历全表以找到匹配的行
index: Full Index Scan,index与ALL区别为index类型只遍历索引树
range:只检索给定范围的行,使用一个索引来选择行
ref: 表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
eq_ref: 类似ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用primary key或者 unique key作为关联条件
const、system: 当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于where列表中,MySQL就能将该查询转换为一个常量,system是const类型的特例,当查询的表只有一行的情况下,使用system
NULL: MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成。
5.possible_keys(指出MySQL能使用哪个索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用)
该列完全独立于EXPLAIN输出所示的表的次序。这意味着在possible_keys中的某些键实际上不能按生成的表次序使用。
如果该列是NULL,则没有相关的索引。在这种情况下,可以通过检查WHERE子句看是否它引用某些列或适合索引的列来提高你的查询性能。如果是这样,创造一个适当的索引并且再次用EXPLAIN检查查询
6.Key(key列显示MySQL实际决定使用的键(索引))
如果没有选择索引,键是NULL。要想强制MySQL使用或忽视possible_keys列中的索引,在查询中使用FORCE INDEX、USE INDEX或者IGNORE INDEX。
7.key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度(key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的)
不损失精确性的情况下,长度越短越好
8.ref
表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
9.rows
表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数
10.Extra
该列包含MySQL解决查询的详细信息,有以下几种情况:
a.Using where:列数据是从仅仅使用了索引中的信息而没有读取实际的行动的表返回的,这发生在对表的全部的请求列都是同一个索引的部分的时候,表示mysql服务器将在存储引擎检索行后再进行过滤
b.Using temporary:表示MySQL需要使用临时表来存储结果集,常见于排序和分组查询
c.Using filesort:MySQL中无法利用索引完成的排序操作称为“文件排序”
d.Using join buffer:改值强调了在获取连接条件时没有使用索引,并且需要连接缓冲区来存储中间结果。如果出现了这个值,那应该注意,根据查询的具体情况可能需要添加索引来改进能。
e.Impossible where:这个值强调了where语句会导致没有符合条件的行。
f.Select tables optimized away:这个值意味着仅通过使用索引,优化器可能仅从聚合函数结果中返回一行
总结:
- EXPLAIN不会告诉你关于触发器、存储过程的信息或用户自定义函数对查询的影响情况
- EXPLAIN不考虑各种Cache
- EXPLAIN不能显示MySQL在执行查询时所作的优化工作
- 部分统计信息是估算的,并非精确值
- EXPALIN只能解释SELECT操作,其他操作要重写为SELECT后查看执行计划。