Mysql优化之创建高性能索引(一)

时间:2021-07-20 19:04:48

1.索引基础

索引对于良好的性能非常关键。尤其是当表中的数据量越来越大时,索引对性能的影响愈发重要。但是不恰当的索引随着数据量的增加,也会使整个数据库的性能下降。

举个例子:

select a from b where id = 5;

如果在id上建立索引,则Mysql会使用该索引找到id为5的行,也就是说,Mysql现在索引按值进行查找,然后返回所有包含该值的数据行。索引也可以包含一列或者多列,列的顺序也十分重要,因为Mysql只能高效地使用索引的最左前缀列。

索引优化应该是查询性能优化最有效的手段了,一个“最优”的索引有时比一个“好的”索引性能要好两个数量级,所以索引的学习无论对开发者或者DBA来说都极为重要。

2.索引类型

B-Tree索引

人们在谈论索引时,若无指定,一般为B-Tree类型的索引,Mysql大部分引擎支持B-Tree索引,但在不同的存储引擎中,B-Tree也可能以不同存储结构实现,比如InnoDB则使用B+Tree。存储引擎以不同的方式使用B-Tree索引,性能优劣各有不同。

B-Tree通常意味着所有的值都是按顺序存储的,并且每一个叶子页到根的距离相同。下图为B-Tree(B+Tree)索引的抽象表示,大致反映了InnoDB索引是如何工作的。MyISAM使用的结构有所不同,但基本思想是类似的。

Mysql优化之创建高性能索引(一)

这种类型的索引的好处是查询时不需要进行全盘扫描而是从根节点开始搜索,通过比较找到相应的值并进入下层子节点。所以B-Tree比较适合查找范围数据,例如像“找出所有I到K开头的名字”。

假设有如下数据表:

CREATE TABLE People (
last_name varcher(
50) not null,
first_name varcher(
50) not null,
dob date not
null,
gender
enum('m', 'f') not null,
key(last_name, first_name, dob)
);

对于表中的每一行数据,索引中包含了last_name,first_name,dob列的值,下图显示了该索引是如何组织数据的存储的。

Mysql优化之创建高性能索引(一)

  • 这里要注意的是,索引对多个值进行排序的依据是CREATE TABLE语句中定义时列的顺序(比如上文的顺序为last_name,first_name,dob)。

下面列举一些B-Tree索引的常用的查询类型:

  • 全值比配 全值匹配指的是和索引中的所有列进行匹配,例如索引可用于查找姓名为Cuba Allen、出生于1960-01-01的人
  • 匹配最左前缀 例如查找所有姓Allen的人,即只使用索引的第一列
  • 匹配列前缀 也可以只匹配某一列的值的开头部分。例如前面提到的索引可用于查找所有以J开头的姓的人。这里也只使用了索引的第一列
  • 匹配范围值 例如前面提到的索引可用于查找姓在Allen和Barrymore之间的人。这里也只使用了索引的第一列
  • 精确匹配某一列并范围匹配另外一列 例如像查找所有姓为Allen,并且名字是字母K开头的人。即第一列last_name全匹配,第二列first_name范围匹配
  • 只访问索引的查询 即查询只需要访问索引,而无须访问数据行。

除了按值查找外,索引还可以用于ORDER BY操作。

下列关于B-Tree索引的一些限制:

  • 如果不是按照索引的最左列开始查找,则无法使用索引。例如上面例子中无法用于查找名字为Bill的人,也无法查找某个特定生日的人,因为这两列都不是最左数据列。
  • 不能跳过索引中的列,例如查找Smith并且在某个特定生日的人,如果不指定first_name,则Mysql只能用上索引的第一列
  • 如果查询中有某个列的范围查询,则其右边的列都无法使用索引优化查找。比如 where last_name='Smith' AND first_name LIKE '%J%' AND dob= '1976-01-01',这个查询只能用上前两列索引,但如果范围查询数量有限,则可以优化为多个等于条件进而使用上索引

哈希索引

哈希索引基于哈希表实现,只有精确匹配索引所有列的查询才有效。在Mysql中只有Memory引擎显式支持哈希索引。

比如: SELECT A FROM B WHERE name='PETER';

Mysql会先计算'PETER'的哈希值,并使用该值寻找对应的指针记录。因为索引自身只需存储对应的哈希值,所以哈希索引查找速度非常快,但是也有一些限制:

  • 哈希索引只包含哈希值和行指针,不存储字段值,所以不能使用索引中的值来避免读取行
  • 哈希索引不是顺序存储,无法用于排序
  • 哈希索引也不支持索引列匹配查找,例如KEY(A,B),如果查询只有数据列A则无法使用该索引
  • 哈希索引只支持等值比较查询
  • 当出现哈希冲突时,存储引擎必须便利链表中所有的行指针,直到找到匹配的行
  • 哈希冲突很多的话,一些索引维护操作的代价也会很高

因为这些限制,哈希索引使用的场景有限,而一旦适合哈希索引时,则它带来的性能提升将非常高。

当然,对于InnoDB来说也可以创建一个伪哈希列来进行排序查找也是可以的。

下面有个实例,例如需要存储大量URL,如果使用B-Tree来存储URL,存储的内容会很大,因为URL本身很长,正常情况下会有如下查询:

SELECT ID FORM URL WHERE url='http://www.baidu.com'

若删除原来的URL列上的索引,而新增一个被索引的url_crc列,使用CRC32做哈希,就可以使用下面的方式查询:

 SELECT ID FROM URL WHERE url='http://www.baidu.com' AND url_crc=CRC32("http://www.baidu.com"); 

这样做的性能会比单独在url列上开索引高很多,但缺陷就是需要维护哈希值,可以手动维护,也可以使用触发器实现,例如:

//创建表
CREATE TABLE pseudo hash(
id
int unsigned not null auto_increment,
url archer(
255) not null,
url_crc
int unsigned not null default 0,
primary key(id)
);
//创建触发器
DELIMITER //
CREATE TRIGGER pseudohash_crc_ins BEFORE INSERT ON pseudohash FOR EACH ROW BEGIN
SET NEW.url_crc
=crc32(NEW.url);
END;
//
CREATE TRIGGER pseudohash_crc_upd BEFORE UPDATE ON pseudohash FOR ROW BEGIN
SET NEW.url_crc
=crc32(NEW.url);
END;
//
DELIMITER ;

如果数据表非常大,crc32可能也会出现大量的哈希冲突,这个时候也可以使用其他方案代替,比如md5

处理哈希冲突时,必须在WHERE子句中包含常量值:

SELECT ID FROM URL WHERE url_crc=crc32('http://www.baidu.com') AND url='http://www.baidu.com';

还有一些其他的索引类型,例如空间数据索引,全文索引,这里暂时不介绍了,大家可以自行搜索。