mysql 索引在 mysql 优化中来说是非常重要的一个环节 。索引本质上不难,但要构建高效的索引却又不是那么容易的。在这里打算分三个环节来描述下索引:
- 索引基础
- 索引的使用
- 索引优化
其中索引基础,就是这篇文章要说的问题,第二部分索引的使用包括前缀索引,全文索引等内容,第三部分想要说的是索引的优化,这里提下全文索引,一起使用全文索引的时候基本上是myql 结合 sphinx 或者 elasticsearch 全文搜索引擎的使用,不过在mysql 5.7 中已内置 ngram 分词插件。
下面是这篇文章的概述:
- 1.理解索引
- 2.索引的类型
- 3.索引的管理
- 4.索引的优缺点
- 5.索引使用的原则
1.理解索引
在说索引前,我们先在脑海中回忆下,我们以前使用新华词典的过程。想一下,我们从词典查找某个词的释义的过程 -- 大多数情况下是先从拼音或部首开始查到该词大概所在的页数,然后把词典翻到相应到页数查到我们需要的数据。如果把这一过程抽象出来,那么我们可以把拼音或部首称之为索引。
细想下,我们要查 "木" 这个词的释义过程,先把词典翻到拼音部分,找到 "m" 部分,然后再在''m" 部分查找 "u" ,再然后找到 "木" 这个词,从而找到 "木" 这个词位于词典的第几页,然后就能迅速地找到改词的释义(也就是我们要查找的数据)。我们把词典想象成mysql数据表,拼音或部首想象成索引,我们可以用上面查词典的过程在脑海中描绘出mysql查数据的过程,是不是很简单(虽然并不很准确,但却很容易理解)。
索引是从数据中提取关键字,并与数据记录建立对应关系的数据结构。索引的本质是数据结构。
2.索引的类型
名称 | 语法 | 关键词 |
主键索引 | primary key | 要求关键字不能重复,也不能为NULL。同时增加主键约束 |
唯一索引 | unique index | 要求关键字不能重复。同时增加唯一约束 |
全文索引 | fulltext key | 关键字的来源不是所有字段的数据,而是从字段中提取的特别关键词 |
普通索引 | index | 对关键字没有要求 |
关键词可以使记录的部分数据(某个字段,某些字段,某个字段的一部分),当是某些字段的时候,那就是复合索引;当是某个字段的一部分的时候,那就是前缀索引。前面四种索引类型都可以为复合索引。
3. 索引的管理
3.1创建索引
3.1.1 更新表结构
先看一下,现有表结构:
mysql> show create table dye_production_schedules \G;
*************************** 1. row ***************************
Table: dye_production_schedules
Create Table: CREATE TABLE `dye_production_schedules` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '染订单id',
`order_detail_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '染订单分录id',
`dye_code` varchar(64) NOT NULL DEFAULT '' COMMENT '染订单编号',
`product_id` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT '产品id',
`batch_num` varchar(64) NOT NULL DEFAULT '' COMMENT '缸号',
`customer_color_name` varchar(128) NOT NULL DEFAULT '' COMMENT '客户颜色',
`is_delete` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否删除,默认 0 没有删除, 1 -删除',
`scheduling_bacth` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '投胚卷数',
`scheduling_qty` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '投胚数量,以最小单位来计算,比如单位是kg ,那么就存g',
`finished_batch` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '成品卷数',
`finished_qty` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '成品重量,以最小单位来计算,比如单位是kg ,那么就存g',
`prodction_state` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '生产进度状态',
`dye_factory_id` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT '染厂id',
`is_pCode` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '0 没有疋号,1 有疋号',
`is_need_knit` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否需要领胚布,0 不需要 ,1 - 需要',
`remark` text COMMENT '备注',
`created_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1011 DEFAULT CHARSET=utf8
更新表结构,加入以下索引:
alter table dye_production_schedules
add index `order_id_order_detail_id` (`order_id`,`order_detail_id`), -- 复合索引
add index `dye_factory_id` (`dye_factory_id`),
add index `dye_code` (`dye_code`),
add index `order_id_batch_num` (`order_id`,`batch_num`) ,
add fulltext index `customer_color_name` (`customer_color_name`);
其中`dye_factory_id` 和 `dye_code` 是普通索引,`customer_color_name` 是全文索引
注:这里没有列出主键索引是之前建表就指定了主键索引,我这边数据库表数据是采用定时任务执行脚本往数据表插数据,如果建立唯一索引,也会对表数据加入唯一约束,所以这里暂不讨论唯一索引,建立唯一索引只需要在index 前加上关键词 unique 即可!
3.1.2 建表时创建索引
create table index_test_table(
id int auto_increment,
order_id int unsigned not null default 0 comment '染订单id',
order_detail_id int unsigned not null default 0 comment '染订单分录id',
dye_code varchar(64) not null default '' comment '染订单编号',
product_id smallint unsigned not null default 0 comment '产品id',
batch_num varchar(64) not null default '' comment '缸号',
customer_color_name varchar(128) not null default '' comment '客户颜色',
is_delete tinyint unsigned not null default 0 comment '是否删除,默认 0 没有删除, 1 -删除',
scheduling_bacth int unsigned not null default 0 comment '投胚卷数',
scheduling_qty bigint unsigned not null default 0 comment '投胚数量,以最小单位来计算,比如单位是kg ,那么就存g',
`finished_batch` int unsigned not null default 0 comment '成品卷数',
`finished_qty` bigint unsigned not null default 0 comment '成品重量,以最小单位来计算,比如单位是kg ,那么就存g',
prodction_state tinyint unsigned not null default 0 comment '生产进度状态',
`dye_factory_id` smallint unsigned not null default 0 comment '染厂id',
is_pCode tinyint unsigned not null default 0 comment '0 没有疋号,1 有疋号',
is_need_knit tinyint unsigned not null default 0 comment '是否需要领胚布,0 不需要 ,1 - 需要',
`remark` text comment '备注',
created_at timestamp,
primary key(id),
index `order_id_order_detail_id` (`order_id`,`order_detail_id`), -- 复合索引
index `dye_factory_id` (`dye_factory_id`),
index `dye_code` (`dye_code`),
index `order_id_batch_num` (`order_id`,`batch_num`) ,
fulltext index `customer_color_name` (`customer_color_name`)
)engine = InnoDB default charset = utf8;
如果你使用过mysql 5.6之前的版本,相信你肯定会对上面的ddl 语句有所疑问(可能在想,不是说mysql innodb 不支持全文索引么?),我这里使用的是mysql 5.7 ,mysql InnoDB 是在5.6后开始支持全文索引的。
在建立索引时,不一定要指定索引名,如果不指定,那么mysql 会自己指定,默认是字段名。我一般喜欢在索引名和字段名加上反引号(``,键盘上 tab 上面的键),这样可以降低出错的概率。
3.2 删除索引
alter table dye_production_schedules
drop index `order_id_order_detail_id`,
drop index `dye_factory_id`,
drop index `dye_code`,
drop index `order_id_batch_num`,
drop index `customer_color_name`;
4.索引的优缺点
在说具体的优缺点前,先看下有索引和没索引查询相应时间的一个区别。
由于数据量小体现不出差异,所以这里写了个定时任务,插入了60多万条数据。使用show index from tableName 可以查看tableName 的索引。
注:本机配置:8G 内存 + SATA 老硬盘, + ubuntu 16.04 系统。
这里是使用主键查询的
mysql> select count(*) from dye_production_schedules;
+----------+
| count(*) |
+----------+
| 622070 |
+----------+
1 row in set (1.86 sec)
mysql> select id ,dye_code ,customer_color_name from dye_production_schedules where id = 40000 ;
+-------+----------------+-----------------------------------------------------------------------------------------------------------+
| id | dye_code | customer_color_name |
+-------+----------------+-----------------------------------------------------------------------------------------------------------+
| 40000 | 2c43a6d572b97a | 0oPYaZ5cqI;她的头略略偏右仰着,嘴唇轻轻的动着,嘴唇以上,尽是微笑。唱Wshyk |
+-------+----------------+-----------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> explain select id ,dye_code ,customer_color_name from dye_production_schedules where id = 40000 ;
+----+-------------+--------------------------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------------------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | dye_production_schedules | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+--------------------------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
这里根据dye_code 这个字段来查询
mysql> select sql_no_cache id ,dye_code ,customer_color_name from dye_production_schedules where dye_code = '2c43a6d572b97a';
+-------+----------------+-----------------------------------------------------------------------------------------------------------+
| id | dye_code | customer_color_name |
+-------+----------------+-----------------------------------------------------------------------------------------------------------+
| 40000 | 2c43a6d572b97a | 0oPYaZ5cqI;她的头略略偏右仰着,嘴唇轻轻的动着,嘴唇以上,尽是微笑。唱Wshyk |
+-------+----------------+-----------------------------------------------------------------------------------------------------------+
1 row in set, 1 warning (1.66 sec)
mysql> select sql_no_cache id ,dye_code ,customer_color_name from dye_production_schedules where dye_code = '2c43a6d572b97a' limit 1;
+-------+----------------+-----------------------------------------------------------------------------------------------------------+
| id | dye_code | customer_color_name |
+-------+----------------+-----------------------------------------------------------------------------------------------------------+
| 40000 | 2c43a6d572b97a | 0oPYaZ5cqI;她的头略略偏右仰着,嘴唇轻轻的动着,嘴唇以上,尽是微笑。唱Wshyk |
+-------+----------------+-----------------------------------------------------------------------------------------------------------+
1 row in set, 1 warning (0.10 sec)
mysql> explain select sql_no_cache id ,dye_code ,customer_color_name from dye_production_schedules where dye_code = '2c43a6d572b97a' limit 1;
+----+-------------+--------------------------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------------------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| 1 | SIMPLE | dye_production_schedules | NULL | ALL | NULL | NULL | NULL | NULL | 471005 | 10.00 | Using where |
+----+-------------+--------------------------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 2 warnings (0.00 sec)
mysql>
不知道你还记不记得,前面第一部分内容说理解索引的时候,举的查词典栗子,说根据查词典流程去理解mysql 查询并不很准确,这是因为mysql在一次查询完毕后,会做很多后续工作,其中就会尝试把查询结果缓存起来,这里的sql_no_cache 是指示mysql 不要对这条sql查询结果进行缓存。
通过explain 可以看到,当没有索引查询的时候,哪怕是指定了limit 1 ,也是全表扫描(type 值为ALL ),至于explain 的用法可以参考 https://blog.csdn.net/zhang_referee/article/details/83041301 。
我们再看一个排序的栗子:
我们按照`order_detail_id` 这个字段来排序:
mysql> select sql_no_cache id ,order_id,order_detail_id ,dye_code ,customer_color_name from dye_production_schedules order by order_detail_id desc limit 10;
+--------+----------+-----------------+----------------+-----------------------------------------------------------------------------------------------------------+
| id | order_id | order_detail_id | dye_code | customer_color_name |
+--------+----------+-----------------+----------------+-----------------------------------------------------------------------------------------------------------+
| 595950 | 1076 | 100000 | e1004ec21380a5 | 35Fe6vimkq 你为我的捞什子书也费了不少神;第一回让你父亲的男佣人从家HyR4O |
| 107857 | 7301 | 100000 | a279af0d418d38 | QOvptosZXN字与字间的时距,我不能指明,只觉比普通人说话延长罢了;最令我USfR9 |
| 249517 | 5463 | 100000 | a7682ca9b246a9 | 3ySBwHWLbG过的荷塘,在这满月的光里,总该另有一番样子吧。月亮渐渐地升高dsZzQ |
| 218765 | 9783 | 100000 | 40a8c5c97ffa0c | ElAJFnO4W7。那边学校当局要我约圣陶去。圣陶来信说:“我们要痛痛快快游西a8k1d |
| 566500 | 3779 | 99999 | be6bc4fb7d2e00 | qjvSLdR6b3字。“人”让他站着,“牛”也让它站着;所饶不过的是“女”人,zcTJ9 |
| 164685 | 5037 | 99999 | 8b737b07d85bb3 | 8eMkXJrWqS快,不觉七点还欠五分了。这时票子还有许多人没买着,大家都着急06RxO |
| 137008 | 1860 | 99999 | 9872f5c73533b5 | C9BaK4wsbm去,直到现在——中间又被朋友拉到福州一次,有一篇《将离》抒写dplOM |
| 370503 | 1334 | 99999 | 5efb7037d40e47 | eH7481tAlK,始终笔直的站着,几乎不曾移过一步,真像石像一般,有着可怕的LN53s |
| 597563 | 7425 | 99999 | 0b95c11a4e30ee | 1POtQT2GdX的地方便是护城河,曼衍开去,曲曲折折,直到平山堂,——这是你oMwJb |
| 507070 | 6182 | 99999 | dd3dc56d921e92 | nbfxzOZy62动都像不是他们自己的。好容易费了二虎之力,居然买了几张票,凭cH8Gp |
+--------+----------+-----------------+----------------+-----------------------------------------------------------------------------------------------------------+
10 rows in set, 1 warning (3.94 sec)
mysql> explain select sql_no_cache id ,order_id,order_detail_id ,dye_code ,customer_color_name from dye_production_schedules order by order_detail_id desc limit 10;
+----+-------------+--------------------------+------------+------+---------------+------+---------+------+--------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------------------+------------+------+---------------+------+---------+------+--------+----------+----------------+
| 1 | SIMPLE | dye_production_schedules | NULL | ALL | NULL | NULL | NULL | NULL | 471005 | 100.00 | Using filesort |
+----+-------------+--------------------------+------------+------+---------------+------+---------+------+--------+----------+----------------+
1 row in set, 2 warnings (0.01 sec)
该查询不仅耗时久,通过explain 可以发现,使用了文件排序。
下面就添加适当的索引来进行比较:
mysql> alter table dye_production_schedules
-> add index `order_id_order_detail_id` (`order_id`,`order_detail_id`), -- 复合索引
-> add index `dye_factory_id` (`dye_factory_id`),
-> add index `dye_code` (`dye_code`),
-> add index `order_detail_id` (`order_detail_id`),
-> add index `order_id_batch_num` (`order_id`,`batch_num`) ,
-> add fulltext index `customer_color_name` (`customer_color_name`);
Query OK, 0 rows affected (1 min 26.31 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> select sql_no_cache id ,dye_code ,order_id,order_detail_id ,customer_color_name from dye_production_schedules where dye_code = 'b5ac612c7a0139' limit 1;
+--------+----------------+----------+-----------------+-----------------------------------------------------------------------------------------------------------+
| id | dye_code | order_id | order_detail_id | customer_color_name |
+--------+----------------+----------+-----------------+-----------------------------------------------------------------------------------------------------------+
| 100423 | b5ac612c7a0139 | 9006 | 54461 | 3CN92UGwlo是逃不了的。我说北平看花,比别处有意思,也正在此。这时候,我Wsrdi |
+--------+----------------+----------+-----------------+-----------------------------------------------------------------------------------------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select sql_no_cache id ,dye_code ,order_id,order_detail_id ,customer_color_name from dye_production_schedules where dye_code = 'b5ac612c7a0139' limit 1;
+----+-------------+--------------------------+------------+------+---------------+----------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------------------+------------+------+---------------+----------+---------+-------+------+----------+-------+
| 1 | SIMPLE | dye_production_schedules | NULL | ref | dye_code | dye_code | 194 | const | 1 | 100.00 | NULL |
+----+-------------+--------------------------+------------+------+---------------+----------+---------+-------+------+----------+-------+
1 row in set, 2 warnings (0.00 sec)
可以发现,在dye_code 上加上索引,查询速度快了很多。
我们再次按照`order_detail_id` 这个字段来排序:
mysql> select sql_no_cache id ,dye_code ,order_id,order_detail_id ,customer_color_name from dye_production_schedules order by order_detail_id desc limit 10;
+--------+----------------+----------+-----------------+-----------------------------------------------------------------------------------------------------------+
| id | dye_code | order_id | order_detail_id | customer_color_name |
+--------+----------------+----------+-----------------+-----------------------------------------------------------------------------------------------------------+
| 595950 | e1004ec21380a5 | 1076 | 100000 | 35Fe6vimkq 你为我的捞什子书也费了不少神;第一回让你父亲的男佣人从家HyR4O |
| 249517 | a7682ca9b246a9 | 5463 | 100000 | 3ySBwHWLbG过的荷塘,在这满月的光里,总该另有一番样子吧。月亮渐渐地升高dsZzQ |
| 218765 | 40a8c5c97ffa0c | 9783 | 100000 | ElAJFnO4W7。那边学校当局要我约圣陶去。圣陶来信说:“我们要痛痛快快游西a8k1d |
| 107857 | a279af0d418d38 | 7301 | 100000 | QOvptosZXN字与字间的时距,我不能指明,只觉比普通人说话延长罢了;最令我USfR9 |
| 597563 | 0b95c11a4e30ee | 7425 | 99999 | 1POtQT2GdX的地方便是护城河,曼衍开去,曲曲折折,直到平山堂,——这是你oMwJb |
| 566500 | be6bc4fb7d2e00 | 3779 | 99999 | qjvSLdR6b3字。“人”让他站着,“牛”也让它站着;所饶不过的是“女”人,zcTJ9 |
| 507070 | dd3dc56d921e92 | 6182 | 99999 | nbfxzOZy62动都像不是他们自己的。好容易费了二虎之力,居然买了几张票,凭cH8Gp |
| 370503 | 5efb7037d40e47 | 1334 | 99999 | eH7481tAlK,始终笔直的站着,几乎不曾移过一步,真像石像一般,有着可怕的LN53s |
| 164685 | 8b737b07d85bb3 | 5037 | 99999 | 8eMkXJrWqS快,不觉七点还欠五分了。这时票子还有许多人没买着,大家都着急06RxO |
| 137008 | 9872f5c73533b5 | 1860 | 99999 | C9BaK4wsbm去,直到现在——中间又被朋友拉到福州一次,有一篇《将离》抒写dplOM |
+--------+----------------+----------+-----------------+-----------------------------------------------------------------------------------------------------------+
10 rows in set, 1 warning (0.11 sec)
mysql> explain select sql_no_cache id ,dye_code ,order_id,order_detail_id ,customer_color_name from dye_production_schedules order by order_detail_id desc limit 10;
+----+-------------+--------------------------+------------+-------+---------------+-----------------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------------------+------------+-------+---------------+-----------------+---------+------+------+----------+-------+
| 1 | SIMPLE | dye_production_schedules | NULL | index | NULL | order_detail_id | 4 | NULL | 10 | 100.00 | NULL |
+----+-------------+--------------------------+------------+-------+---------------+-----------------+---------+------+------+----------+-------+
通过explain 分析得知,在没有索引的时候,按照`order_detail_id` 这个字段来排序,会做全表扫描文件排序(哪怕是把整个表载入内存做排序也是文件排序,因为explain 并不会告诉你这个区别,具体explain 用法可参考 :https://blog.csdn.net/zhang_referee/article/details/83041301),而在排序字段上加了索引后,mysql 已无需再去做耗时的排序操作。
mysql> select count(*) from dye_production_schedules;
+----------+
| count(*) |
+----------+
| 622070 |
+----------+
1 row in set (0.17 sec)
mysql> explain select count(*) from dye_production_schedules;
+----+-------------+--------------------------+------------+-------+---------------+----------------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------------------+------------+-------+---------------+----------------+---------+------+--------+----------+-------------+
| 1 | SIMPLE | dye_production_schedules | NULL | index | NULL | dye_factory_id | 2 | NULL | 471005 | 100.00 | Using index |
+----+-------------+--------------------------+------------+-------+---------------+----------------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
优缺点:
- 索引能大大提高了查询速度,却会降低更新表的速度,如对表进行insert、update和delete。因为更新表时,不仅要保存数据,还要维护索引。
- 建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果你在一个大表上创建了多种组合索引,索引文件的会增长很快。
在mysql 优化中,性价比最高的就是建立索引,建立索引不难,建立合适的索引(三星索引,这一说法来自于《高性能mysql》),却不总是那么容易的。
文章参考自
《高性能mysql 第三版》 (下载地址:https://pan.baidu.com/s/1haFdY7c9xb6VNtlfPUaidQ)
mysql 官方手册: https://dev.mysql.com/doc/refman/5.7/en/explain-output.html