1. 程式人生 > 實用技巧 >如何確定字首索引的長度?

如何確定字首索引的長度?

為什麼需要字首索引

問題

我們在對一張表裡的某個欄位或者多個欄位建立索引的時候,是否遇到過這個問題。

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),還是要根據業務設定每個欄位的長度,太長了也不利於我們建立聯合索引。

解決辦法

  1. 可以直接去改欄位的長度,或者說,把索引的欄位取消掉一些,但是這樣改對錶本身是不友好的。

  2. 通過限定欄位的前n個字元為索引,可以通過衡量實際的業務中資料中的長度來取具體的值。

    UNIQUE KEY `uniq_code` (`nick_name`(20),`account`(20),`city`(20))
    複製程式碼

    表示三個欄位取前20字元作為唯一索引,這樣的話就是長度就不會超出,這個就是我們說的字首索引

  3. 修改單個索引的最大長度

    修改索引限制長度需要在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字首索引長度確定方法 - 簡書

mysql索引長度的一些限制 - yuyue2014 - 部落格園

MySQL的型別與資料長度 - 掘金


作者:何小H
連結:https://juejin.im/post/6844904200254521351
來源:掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。