MySQL 不允许索引 BLOB、TEXT 和长 VARCHAR 列的完整值,因为它们包含的数据可能很大,并且隐式 DB 索引会很大,这意味着索引没有任何好处。
MySQL 要求您定义要索引的前 N 个字符,诀窍是选择一个足够长的数字 N,以提供良好的选择性,但又足够短以节省空间。前缀应该足够长,以使索引几乎与索引整个列时一样有用。
在我们继续之前,让我们定义一些重要的术语。 索引选择性是总的不同索引值与总行数的比率。以下是测试表的一个示例:
+-----+-----------+
| id | value |
+-----+-----------+
| 1 | abc |
| 2 | abd |
| 3 | adg |
+-----+-----------+
如果我们只索引第一个字符(N=1),那么索引表将如下表所示:
+---------------+-----------+
| indexedValue | rows |
+---------------+-----------+
| a | 1,2,3 |
+---------------+-----------+
在这种情况下,索引选择性等于 IS=1/3 = 0.33。
现在让我们看看如果将索引字符数增加到两个 (N=2) 会发生什么。
+---------------+-----------+
| indexedValue | rows |
+---------------+-----------+
| ab | 1,2 |
| ad | 3 |
+---------------+-----------+
在这种情况下 IS=2/3=0.66,这意味着我们增加了索引选择性,但我们也增加了索引的大小。诀窍是找到将导致最大索引选择性的最小数 N。
您可以通过两种方法对数据库表进行计算。我会在this database dump做示范。
假设我们想要将表 employees 中的列 last_name 添加到索引中,并且我们想要定义最小的数字 N 这将产生最佳的索引选择性。
首先让我们确定最常见的姓氏:
select count(*) as cnt, last_name
from employees
group by employees.last_name
order by cnt
+-----+-------------+
| cnt | last_name |
+-----+-------------+
| 226 | Baba |
| 223 | Coorg |
| 223 | Gelosh |
| 222 | Farris |
| 222 | Sudbeck |
| 221 | Adachi |
| 220 | Osgood |
| 218 | Neiman |
| 218 | Mandell |
| 218 | Masada |
| 217 | Boudaillier |
| 217 | Wendorf |
| 216 | Pettis |
| 216 | Solares |
| 216 | Mahnke |
+-----+-------------+
15 rows in set (0.64 sec)
如您所见,姓氏Baba 是最常见的。现在我们要找出最常出现的 last_name 前缀,从五个字母的前缀开始。
+-----+--------+
| cnt | prefix |
+-----+--------+
| 794 | Schaa |
| 758 | Mande |
| 711 | Schwa |
| 562 | Angel |
| 561 | Gecse |
| 555 | Delgr |
| 550 | Berna |
| 547 | Peter |
| 543 | Cappe |
| 539 | Stran |
| 534 | Canna |
| 485 | Georg |
| 417 | Neima |
| 398 | Petti |
| 398 | Duclo |
+-----+--------+
15 rows in set (0.55 sec)
每个前缀出现的次数要多得多,这意味着我们必须增加数字 N,直到值几乎与上一个示例中的值相同。
这里是 N=9 的结果
select count(*) as cnt, left(last_name,9) as prefix
from employees
group by prefix
order by cnt desc
limit 0,15;
+-----+-----------+
| cnt | prefix |
+-----+-----------+
| 336 | Schwartzb |
| 226 | Baba |
| 223 | Coorg |
| 223 | Gelosh |
| 222 | Sudbeck |
| 222 | Farris |
| 221 | Adachi |
| 220 | Osgood |
| 218 | Mandell |
| 218 | Neiman |
| 218 | Masada |
| 217 | Wendorf |
| 217 | Boudailli |
| 216 | Cummings |
| 216 | Pettis |
+-----+-----------+
这是 N=10 的结果。
+-----+------------+
| cnt | prefix |
+-----+------------+
| 226 | Baba |
| 223 | Coorg |
| 223 | Gelosh |
| 222 | Sudbeck |
| 222 | Farris |
| 221 | Adachi |
| 220 | Osgood |
| 218 | Mandell |
| 218 | Neiman |
| 218 | Masada |
| 217 | Wendorf |
| 217 | Boudaillie |
| 216 | Cummings |
| 216 | Pettis |
| 216 | Solares |
+-----+------------+
15 rows in set (0.56 sec)
这是非常好的结果。这意味着我们可以在列last_name 上建立索引,只索引前 10 个字符。在表定义列中,last_name 定义为VARCHAR(16),这意味着我们为每个条目节省了 6 个字节(如果姓氏中有 UTF8 字符,则保存更多字节)。在这个表中有 1637 个不同的值乘以 6 个字节大约是 9KB,想象一下如果我们的表包含数百万行,这个数字会如何增长。
您可以在我的帖子Prefixed indexes in MySQL中阅读其他计算N数量的方法。