MySQL 支持前缀索引,也就是说可以定义字符串的一部分作为索引。默认情况下,创建索引的语句不指定前缀长度,那么索引就会包含整个字符串。
举个例子,我们的数据库表中有一个字段存储的邮箱地址,类型是字符串。
1 | alter table t add index index_1(email); |
前缀索引只取了字符串的一部分字节,因此索引占用的内存空间会变小,这是使用前缀索引的优势。但是可能会增加额外的记录扫描次数,因为可能多个字符串索引的前缀是相同的。
因此,使用前缀索引,定义好一个合理的长度。就可以做到既节省内存,又不用额外增加太多的查询成本。
一、如何建立字符串前缀索引
我们在建立索引时需要关注区分度,区分度越高,意味着重复的键值就越少。我们可以通过 distinct
来查看某个列上有多少个不同的值,以及不同前缀长度的这个列上的不同值的数量。
1 | select count(distinct email) as L from table_name; |
当然,使用前缀索引可能会损失区分度,所以需要预先设定一个可以接受的损失比例。
二、前缀索引对覆盖索引的影响
对于覆盖索引来说,从索引中即可获取到所需的信息,不需要再通过主键索引再查一次。
但如果是前缀索引的话,就用不上覆盖索引对于查询性能的优化,还是需要通过主键索引多查一次。
三、优化方式
倒序存储
对于不同的场景,比如前缀重复的较多,而后缀重复的较少,可以使用倒序存储,也就是存储的时候使用倒序存储,查询的倒序查询。
1 | select field_list from t where id_card = reverse('input_id_card_string'); |
当然做之前最好使用 count(distinct)
方法去做验证。
增加辅助字段(hash的方式)
假设我们要查询的是字段A。可以在表上增加一个整数字段,来保存字段 A 的校验码,同时在这个整数字段上创建索引。
然后我们每次插入新记录的时候,都计算校验码(比如 crc32)填到这个整数字段。
由于校验码可能相同,而引起冲突。因为我们在查询字段 A 时,需要精确匹配字段 A。
1 | select field_list from t where id_card_crc=crc32('input_id_card_string') and id_card='input_id_card_string'; |
这样的话,索引的长度就变小了很多。
以上方法的对比
- 从占用的额外空间来看,倒序存储方式在主键索引上,不会消耗额外的存储空间;而增加辅助字段 方式需要增加一个字段。当然,倒序存储方式,也需要建立前缀索引,前缀索引的长度可能大于辅助字段的长度,可能存在额外的存储空间。
- 从 CPU 消耗来看,倒序方式每次写和读的时候,都需要额外调用一次 reverse 函数,而 增加辅助字段方式则需要调用 生成校验码的算法函数(比如 crc32)。相比来说,reverse 函数的 cpu 消耗会小一点。
- 从查询效率上来看,使用 辅助字段的方式的查询性能相对更稳定一点。虽然校验码有冲突的概率,但是概率非常小。可以认为每次查询的平均扫描行数接近 1。而倒序存储方式毕竟还是用前缀索引的方式,也就是说还是会增加扫描行数。
四、小结
对于字符串字段创建索引的场景,我们可以使用的方式有:
- 直接创建完整索引,这样可能比较占空间
- 创建前缀索引,节省空间,但会增加查询扫描次数,并且不能使用覆盖索引
- 倒序存储后,再创建前缀索引,可以绕过字符串本身前缀的区分度不够的问题。还不支持范围扫描,因为倒序存储的字段上创建的索引是按照倒序字符串的方式排序的,已经没有办法利用索引查询范围了。
- 增加辅助字段,并创建辅助字段索引的方式,查询性能稳定,但有额外的存储和计算消耗。还不支持范围扫描,因为校验字段是无序的,只能支持等值查询。