在MySQL中理解索引

5
我正在尝试理解MySQL中的索引。我知道在表中创建索引可以加快执行查询的速度,但会减慢插入和更新行的速度。在创建索引时,我在名为authors的表上使用了这个查询(包含AuthorNumAuthorFNameAuthorLName等字段)。
Create index Index_1 on Authors ([What to put here]);

我知道我需要输入一个列名,但是是哪个列名呢?

当用户查询表时,在Where语句中必须输入将要进行比较的列名吗?


通常情况下(但不总是),在大多数情况下,索引是由“where”确定的。用户如何搜索“作者”?按名字、姓氏等方式? - StuartLC
我会假设他们将搜索作者的名字。 - MrCSharp
1
索引不仅仅是从一个列中构建的,有些可能是从多个列中构建的,而其他一些可能只是从某些列中的信息构建的。例如,如果您有一个完整的“datetime”列,但您知道您只会按“date”筛选记录,则可以基于“datetime”列构建一个索引,但只包含“date”信息。 - Mihai Stancu
5个回答

9

索引的解剖

索引是数据库中的一个独立数据结构,数据冗余。它的主要目的是通过逻辑排序提供索引数据的有序表示,该排序与物理排序无关,使用双向链表和称为平衡搜索树(B 树)的树结构来实现。B 树非常好用,因为它们保持数据排序并允许在对数时间内进行搜索、访问、插入和删除。由于双向链表,我们能够轻松地在索引上向前或向后进行各种查询。插入变得简单,因为我们只需要重新排列指向不同数据片段的指针。数据库使用这些双向链表将叶子节点(通常位于 B+ 树或 B 树中)连接起来,每个叶子节点都存储在一个页面中,并在叶子节点之间建立逻辑排序。更新或插入等操作会变慢,因为它们实际上是文件系统中的两个写操作(一个用于表数据,一个用于索引数据)。

使用 WHERE 定义最佳索引

要定义最佳索引,你必须不仅了解索引的工作原理,还必须了解应用程序如何查询数据。例如,你必须知道出现在 WHERE 子句中的列组合。

查询 LAST_NAME 和 FIRST_NAME 列的常见限制与大小写敏感性有关。例如,我们希望与所有结果匹配,如 HoTingEr 等,而不是像 Hotinger 这样进行精确搜索。在 WHERE 子句中这很容易实现:我们只需说 WHERE UPPER(LAST_NAME) = UPPER('Hotinger')

然而,如果我们定义了一个LAST_NAME的索引并进行查询,实际上会执行全表扫描,因为查询不是在LAST_NAME上进行的,而是在UPPER(LAST_NAME)上进行的。从数据库的角度来看,这是完全不同的。因此,在这种情况下,应该改为在UPPER(LAST_NAME)上定义索引。

索引不一定只针对一个列。例如,如果主键是复合键(由多个列组成),则会创建连接索引,也称为组合索引。请注意,连接索引的排序对其可用性和可扩展性有重大影响,因此必须谨慎选择。基本上,排序应与WHERE子句中的排序方式相匹配。

使用LIKE定义最佳索引

通配符字符的位置非常重要。LIKE子句仅在树遍历期间使用通配符之前的字符;其余字符不会缩小扫描索引范围。前缀越具有选择性,LIKE子句扫描的索引范围就越窄。这使得索引查找更快。提示一下,避免以通配符开头的LIKE子句,例如"%OTINGER%"。对于全文搜索,MySQL提供了MATCHAGAINST关键字。从MySQL 5.6开始,您可以使用全文索引。有关这些结果的索引的更深入讨论,请查看MySQL的全文搜索函数


2

长度限制的索引:

使用 text 列或非常大的 varchar 列时,您将无法在整个 text/varchar 长度上创建索引,因为有一些限制(约为1024个ASCII字符的长度)。

在这种情况下,您需在索引声明中指定长度。

CREATE INDEX `my_limited_length_index` ON `my_table`(`long_text_content`(512));
-- please notice the use of the numeric length of the index after the column name

处理后的值索引(显然只在PostgreSQL而非MySQL中可用):

索引不仅仅是从一个列中构建的,有些可能是从多个列中构建的,还有一些可能只是从某些列的部分信息中构建的。例如,如果您有一个完整的datetime列,但您知道您只会通过date过滤记录,则可以基于datetime列构建一个索引,但只包含date信息。

-- `my_table` has a `created` column of type timestamp
CREATE INDEX `my_date_created` ON `my_table`(DATE(`created`));
-- please notice the use of the DATE function which extracts only
-- the date from the `created` timestamp

2
通常情况下,您需要在查询的WHERE子句中比较的列上创建索引以加快查询速度。
如果按AuthorFName搜索,则在该列上创建索引。如果按AuthorLName搜索,则在该列上创建索引。
但在这种情况下,您应该考虑使用FULLTEXT索引。这将允许用户输入模糊查询,返回按相关性排序的多个结果。
来自MySQL手册
索引用于快速查找具有特定列值的行。 如果没有索引,MySQL必须从第一行开始,然后读取整个表以查找相关的行。表越大,成本越高。如果表中有关键列的索引,MySQL可以快速确定要在数据文件中查找的位置,而无需查看所有数据。如果表有1000行,则至少比顺序读取快100倍。如果需要访问大多数行,则顺序读取更快,因为这可以最小化磁盘寻道。

2
一个索引通常指的是B-Tree。了解B-Tree的结构,你就能理解索引能做什么,不能做什么。
在你的特定情况下:
  • WHERE AuthorLName = 'something'WHERE AuthorLName LIKE 'something%'可以通过在{AuthorLName}上建立索引来加速。
  • WHERE AuthorLName = 'something AND AuthorFName = 'something else'可以通过在{AuthorLName,AuthorFName}或者{AuthorFName,AuthorLName}上建立复合索引来加速。
  • WHERE AuthorLName = 'something OR AuthorFName = 'something else'(虽然没有太多意义,但这里用作示例)可以通过建立两个索引:在{AuthorLName}和{AuthorFName}中,来加速。
  • WHERE AuthorLName LIKE '%something'无法通过B-Tree索引来加速(请考虑全文索引)。
  • 等等...
请参阅Use The Index, Luke!,获取比简单的SO帖子更详尽的主题处理方法。

0

索引应跨越您将在WHERE语句中使用的列。

为了更好地理解,这里有一个例子:

SELECT * FROM Authors WHERE AuthorNum > 10 AND AuthorLName LIKE 'A%';
SELECT * FROM Authors WHERE AuthorLName LIKE 'Be%';

如果您经常使用上述查询,建议您拥有两个索引:

Create index AuthNum_AuthLName_Index on Authors (AuthorNum, AuthorLName);
Create index AuthLName_Index on Authors (AuthorLName);

重要的一点是:索引应该具有与WHERE语句中使用的列相同的组合。

这个答案部分是不正确的,如果我理解正确的话。在where子句中表达式的顺序与索引列的适当顺序无关。where子句中的表达式可以以任何逻辑上有效的顺序由服务器进行评估。 - Michael - sqlbot
1
据我理解,索引列将是存储在服务器RAM中的哈希表。在查询时,它会使用WHERE子句中的哈希值搜索存储在RAM中的哈希表,并获取指向行条目的实际指针。 - alandarev
@Michael-sqlbot 谢谢你指出来,我已经修改了答案。从回答问题中学习新知识真是太好了。 - alandarev
1
@alandarev哈希表为所有记录生成和存储哈希值。在比较相等性时,where子句中提供的值以相同的方式(使用相同的哈希函数)进行哈希处理,然后与表中记录的哈希值进行比较。这意味着哈希表索引不能用于比较或相似性,只能用于严格的相等性。这意味着在需要比较时将使用B树索引。 - Mihai Stancu
在上述查询中,只需要一个按{AuthorLName,AuthorNum}(按顺序)组合的索引即可。每增加一个索引都会对INSERT / UPDATE / DELETE性能和存储/缓存造成负面影响。 - Branko Dimitrijevic
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接