為什么需要前綴索引
問題
我們在對一張表里的某個字段或者多個字段建立索引的時候,是否遇到過這個問題。
Specified key 'uniq_code' was too long; max key length is 767 bytes.
表結構如下:
create table `t_account`( `id` BIGINT(20) UNSIGNED NOT NULL auto_increment COMMENT '自增ID', `date` varchar(50) NOT NULL DEFAULT '' COMMENT '日期', `nick_name` varchar(50) NOT NULL DEFAULT '' COMMENT '昵稱', `account` varchar(50) NOT NULL DEFAULT '' COMMENT '賬號', `city` varchar(100) NOT NULL DEFAULT '' COMMENT '城市', ... PRIMARY KEY (`id`), UNIQUE KEY `uniq_code` (`nick_name`,`account`,`city`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Test'; 復制代碼
原因
在MySQL5.6里默認 innodb_large_prefix=0 限制單列索引長度不能超過767bytes。
在MySQL5.7里默認 innodb_large_prefix=1 解除了767bytes長度限制,但是單列索引長度最大還是不能超過3072bytes。
至於為什么是767字節,是依賴於具體的存儲引擎實現的,找了官方文檔,也沒說為啥。 https://dev.mysql.com/doc/refman/8.0/en/create-index.html
varchar(n)占用幾個字節跟字符集有關系:
字符類型若為gbk,每個字符占用2個字節,
字符類型若為utf8,每個字符最多占用3個字節,
字符類型若為utf8mb4,每個字符最多占用4個字節
復制代碼
這里我設置的編碼為utf8mb4編碼,一個字符是占了4個字節,而我創建的索引50+50+100=200字符,總共就是800字節,所以超出了長度。
所以我們經常會見到把字段設置成varchar(255)長度的,在utf8字符集下這個是最大不超過767bytes的長度了,但是並不是一定要設置成varchar(255),還是要根據業務設置每個字段的長度,太長了也不利於我們建立聯合索引。
解決辦法
-
可以直接去改字段的長度,或者說,把索引的字段取消掉一些,但是這樣改對表本身是不友好的。
-
通過限定字段的前n個字符為索引,可以通過衡量實際的業務中數據中的長度來取具體的值。
UNIQUE KEY `uniq_code` (`nick_name`(20),`account`(20),`city`(20)) 復制代碼
表示三個字段取前20字符作為唯一索引,這樣的話就是長度就不會超出,這個就是我們說的
前綴索引
-
修改單個索引的最大長度
修改索引限制長度需要在my.ini配置文件中添加以下內容,並重啟: #修改單列索引字節長度為767的限制,單列索引的長度變為3072 innodb_large_prefix=1 但是開啟該參數后還需要開啟表的動態存儲或壓縮: 系統變量innodb_file_format為Barracuda ROW_FORMAT為DYNAMIC或COMPRESSED 復制代碼
如何確定前綴索引的長度
上面我們說到可以通過前綴索引
來解決索引長度超出限制的問題,但是我們改如何確定索引字段取多長的前綴才合適呢?
這里我們可以通過計算選擇性來確定前綴索引的選擇性,計算方法如下
全列選擇性:
SELECT COUNT(DISTINCT column_name) / COUNT(*) FROM table_name;
某一長度前綴的選擇性:
SELECT COUNT(DISTINCT LEFT(column_name, prefix_length)) / COUNT(*) FROM table_name;
當前綴的選擇性越接近全列選擇性的時候,索引效果越好。
前綴索引的優缺點
- 占用空間小且快
- 無法使用前綴索引做 ORDER BY 和 GROUP BY
- 無法使用前綴索引做覆蓋掃描
- 有可能增加掃描行數
比如身份證加索引,可以加哈希索引或者倒序存儲后加前綴索引。
再談聯合索引的創建
當我們不確定在一張表上建立的聯合索引應該以哪個字段作為第一列時,上面的創建規則同樣適用。
下面這個例子就是在建立customer_id,staff_id的聯合索引時進行判斷,最終選擇(customer_id,staff_id)這樣的組合。
# staff_id_selectivity: 0.0001 # customer_id_selectivity: 0.0373 # COUNT(*): 16049 # 通過結果發現,customer_id 的選擇性更高,所以應該選擇 customer_id 作為聯合索引的第一列 SELECT COUNT(DISTINCT staff_id)/COUNT(*) as staff_id_selectivity, COUNT(DISTINCT customer_id)/COUNT(*) as customer_id_selectivity, COUNT(*) FROM payment 復制代碼
所以說
當索引選擇性越接近全列選擇性的時候,索引效果越好。
也就是用此字段創建索引時,它在這個表的數據里區分度更加明顯。
參考
作者:何小H
鏈接:https://juejin.im/post/6844904200254521351
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。