mysql 索引之索引基礎
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