爲什麼需要前綴索引
問題
我們在對一張表裏的某個字段或者多個字段建立索引的時候,是否遇到過這個問題。
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
所以說
當索引選擇性越接近全列選擇性的時候,索引效果越好。
也就是用此字段創建索引時,它在這個表的數據裏區分度更加明顯。
參考
mysql索引長度的一些限制 - yuyue2014 - 博客園