什么时候应该使用复合索引?


133
  1. 什么时候应该在数据库中使用复合索引?
  2. 使用复合索引对性能有何影响?
  3. 为什么要使用复合索引?

例如,我有一张homes桌子:

CREATE TABLE IF NOT EXISTS `homes` (
  `home_id` int(10) unsigned NOT NULL auto_increment,
  `sqft` smallint(5) unsigned NOT NULL,
  `year_built` smallint(5) unsigned NOT NULL,
  `geolat` decimal(10,6) default NULL,
  `geolng` decimal(10,6) default NULL,
  PRIMARY KEY  (`home_id`),
  KEY `geolat` (`geolat`),
  KEY `geolng` (`geolng`),
) ENGINE=InnoDB  ;

geolat和都使用复合索引对我来说是否有意义geolng

我取代:

  KEY `geolat` (`geolat`),
  KEY `geolng` (`geolng`),

与:

KEY `geolat_geolng` (`geolat`, `geolng`)

如果是这样的话:

  • 为什么?
  • 使用复合索引对性能有何影响?

更新:

由于许多人说它完全取决于我执行的查询,因此以下是执行的最常见的查询:

SELECT * FROM homes
WHERE geolat BETWEEN ??? AND ???
AND geolng BETWEEN ??? AND ???

更新2:

使用以下数据库架构:

CREATE TABLE IF NOT EXISTS `homes` (
  `home_id` int(10) unsigned NOT NULL auto_increment,
  `primary_photo_group_id` int(10) unsigned NOT NULL default '0',
  `customer_id` bigint(20) unsigned NOT NULL,
  `account_type_id` int(11) NOT NULL,
  `address` varchar(128) collate utf8_unicode_ci NOT NULL,
  `city` varchar(64) collate utf8_unicode_ci NOT NULL,
  `state` varchar(2) collate utf8_unicode_ci NOT NULL,
  `zip` mediumint(8) unsigned NOT NULL,
  `price` mediumint(8) unsigned NOT NULL,
  `sqft` smallint(5) unsigned NOT NULL,
  `year_built` smallint(5) unsigned NOT NULL,
  `num_of_beds` tinyint(3) unsigned NOT NULL,
  `num_of_baths` decimal(3,1) unsigned NOT NULL,
  `num_of_floors` tinyint(3) unsigned NOT NULL,
  `description` text collate utf8_unicode_ci,
  `geolat` decimal(10,6) default NULL,
  `geolng` decimal(10,6) default NULL,
  `display_status` tinyint(1) NOT NULL,
  `date_listed` timestamp NOT NULL default CURRENT_TIMESTAMP,
  `contact_email` varchar(100) collate utf8_unicode_ci NOT NULL,
  `contact_phone_number` varchar(15) collate utf8_unicode_ci NOT NULL,
  PRIMARY KEY  (`home_id`),
  KEY `customer_id` (`customer_id`),
  KEY `city` (`city`),
  KEY `num_of_beds` (`num_of_beds`),
  KEY `num_of_baths` (`num_of_baths`),
  KEY `geolat` (`geolat`),
  KEY `geolng` (`geolng`),
  KEY `account_type_id` (`account_type_id`),
  KEY `display_status` (`display_status`),
  KEY `sqft` (`sqft`),
  KEY `price` (`price`),
  KEY `primary_photo_group_id` (`primary_photo_group_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=8 ;

使用以下SQL:

EXPLAIN SELECT  homes.home_id,
                    address,
                    city,
                    state,
                    zip,
                    price,
                    sqft,
                    year_built,
                    account_type_id,
                    num_of_beds,
                    num_of_baths,
                    geolat,
                    geolng,
                    photo_id,
                    photo_url_dir
            FROM homes
            LEFT OUTER JOIN home_photos ON homes.home_id = home_photos.home_id
                AND homes.primary_photo_group_id = home_photos.home_photo_group_id
                AND home_photos.home_photo_type_id = 2
            WHERE homes.display_status = true
            AND homes.geolat BETWEEN -100 AND 100
            AND homes.geolng BETWEEN -100 AND 100

EXPLAIN返回:

id  select_type  table        type  possible_keys                                    key                  key_len  ref     rows  Extra
----------------------------------------------------------------------------------------------------------
1   SIMPLE       homes        ref   geolat,geolng,display_status                     display_status       1        const   2     Using where
1  SIMPLE        home_photos  ref   home_id,home_photo_type_id,home_photo_group_id   home_photo_group_id  4        homes.primary_photo_group_id   4  

我不太了解如何阅读EXPLAIN命令。这看起来好还是坏。现在,我没有为geolat和geolng使用复合索引。我可以做?

Answers:


111

使用组合索引时,应使用组合索引。如下所示的复合索引:

index( column_A, column_B, column_C )

使用这些字段进行联接,过滤和有时选择的查询将受益匪浅。使用该组合中最左边的列子集的查询也将受益。因此上述索引也将满足需要的查询

index( column_A, column_B, column_C )
index( column_A, column_B )
index( column_A )

但这不会(至少不能直接解决,如果没有更好的索引,可能会有所帮助)对需要查询的查询有所帮助

index( column_A, column_C )

请注意如何缺少column_B。

在您的原始示例中,两个维度的复合索引将对查询两个维度或最左边的维度本身(而不是最右边的维度)的查询大有裨益。如果您始终在查询两个维度,那么复合索引是必经之路,并不重要(首先是哪个)。


1
马克,我已经更新了原始帖子(更新2)。这是我的实际查询。我的实际数据库架构。以及EXPLAIN命令返回的内容。因此,根据此信息-我应该使用复合索引。我还不清楚。提前致谢。
Teddy

马克,您的答案中的综合索引是否满足index(column_C)?
Boris D. Teoharov

我不确定我是否理解您的问题。但是,如果您要问index(A,B,C)是否对在C列上进行过滤的查询有帮助,答案通常是“否”,它将不使用索引进行过滤。但是,如果仅选择ABC的一个子集,则可以使用索引消除表扫描。因此,这是不同的,但相关。但是对于索引启用过滤的典型用法,答案是否定的。
Mark Canlas

1
-1,因为一个综合指数确实与帮助WHERE geolat BETWEEN ??? AND ??? AND geolng BETWEEN ??? AND ???。它将在第一个字段之后停止。来自“问题溢出”的答案解释了原因。
里克·詹姆斯

1
@felwithe MySQL在查询中的每个表中只能使用一个索引(有例外项,例如索引的合并)。理想情况下,这意味着查询中的表必须对所有where-子句,表联接,group-by和order-by使用单个索引。因此,每列上的单独索引可能并不总是有效,但是复合索引可以解决问题。
AKHIL MATHEW

56

假设您有以下三个查询:

查询一:

SELECT * FROM homes WHERE `geolat`=42.9 AND `geolng`=36.4

查询II:

SELECT * FROM homes WHERE `geolat`=42.9

查询III:

SELECT * FROM homes WHERE `geolng`=36.4

如果每列有单独的索引,则所有三个查询都将使用索引。在MySQL中,如果您有复合索引(geolatgeolng),只有查询我和查询II(它是使用组合大指数的第一部分)使用索引。在这种情况下,查询III需要全表搜索。

多列索引上手册的“部分中,明确说明了多列索引是如何工作的,因此,我不想重新键入手册。

从“ MySQL参考手册”页面

可以将多列索引视为包含通过连接索引列的值而创建的值的排序数组。

如果对geolat和geolng列使用分隔索引,则表中有两个不同的索引,可以独立搜索。

INDEX geolat
-----------
VALUE RRN
36.4  1
36.4  8
36.6  2
37.8  3
37.8  12
41.4  4

INDEX geolng
-----------
VALUE RRN
26.1  1
26.1  8
29.6  2
29.6  3
30.1  12
34.7  4

如果使用复合索引,则两列都只有一个索引:

INDEX (geolat, geolng)
-----------
VALUE      RRN
36.4,26.1  1
36.4,26.1  8
36.6,29.6  2
37.8,29.6  3
37.8,30.1  12
41.4,34.7  4

RRN是相对记录号(为简化起见,您可以说ID)。前两个索引分别生成,而第三个索引是复合索引。如您所见,由于复合材料1由geolat索引,因此您可以基于geolng进行搜索,但是可以通过geolat或“ geolat AND geolng”进行搜索(因为geolng是二级索引)。

另外,请参阅“ MySQL如何使用索引”手册部分。


1
实际上,我没有任何这些查询。我的查询列在原始帖子中。我的查询是要在方格内返回房屋。我了解空间,并且我不尝试计算距离。我只想知道在尝试显示特定地理网格(例如,邻里/城市/县)内的所有房屋时使用复合索引是否有意义
Teddy

Eyazici,我更新了我的原始帖子(更新2)。这是我的实际查询。我的实际数据库架构。以及EXPLAIN命令返回的内容。因此,根据此信息-我应该使用复合索引。我还不清楚。在此先感谢您
泰迪

@“实际上,我没有任何疑问。”。实际上,我已经使用简单的WHERE条件来解释基本逻辑。在列上使用条件(即WHERE)时,MySQL会尝试尽可能使用索引。“ a AND b之间的x”类似于“ x> a AND x <b”。您在条件查询中同时使用了geolng和geolat列。如果您使用复合索引“(geolat,geolng)”,则您的“ AND geolng between ??? AND ???” 有条件的不会获得索引的优势(这对于MySQL)。因此,对于您的方案,应在每列使用单独的索引。
Emre Yazici

我不明白 当我总是执行包含两个列的查询时,为什么要对geolat和geolng使用单独的索引
Teddy

1
不会。遇到“范围”(如BETWEEN)时,将不考虑索引的其他字段!因此综合指数并没有更好。
里克·詹姆斯

19

关于复合索引的作用可能存在误解。许多人认为,只要where子句涵盖了索引列(在您的情况下geolat和),组合索引就可以用于优化搜索查询geolng。让我们更深入地研究:

我相信您关于房屋坐标的数据将是随机小数,例如:

home_id  geolat  geolng
   1    20.1243  50.4521
   2    22.6456  51.1564
   3    13.5464  45.4562
   4    55.5642 166.5756
   5    24.2624  27.4564
   6    62.1564  24.2542
...

由于geolatgeolng值很难重复出现。上的一个综合指数geolat,并geolng会是这个样子:

index_id  geolat  geolng
   1     20.1243  50.4521
   2     20.1244  61.1564
   3     20.1251  55.4562
   4     20.1293  66.5756
   5     20.1302  57.4564
   6     20.1311  54.2542
...

因此,复合索引的第二列基本上是无用的!使用复合索引的查询速度可能与仅针对该geolat列的索引相似。

如Will所述,MySQL提供了空间扩展支持。空间点存储在单个列中,而不是两个单独的lat lng列中。空间索引可以应用于这样的列。但是,根据我的个人经验,效率可能会被高估。可能是空间索引不能解决二维问题,而只能使用带有二次分裂的R树加快搜索速度

权衡是,空间点使用八字节的双精度数字来存储坐标,因此会消耗更多的内存。如果我错了,请纠正我。


5

复合索引非常强大,因为它们:

  • 加强结构完整性
  • 启用对FILTERED ID的排序

加强结构完整性

复合索引不仅仅是索引的另一种类型。他们可以通过强制将完整性作为主键来为表提供NECESSARY结构。

Mysql的Innodb支持群集,下面的示例说明了为什么可能需要复合索引。

要创建朋友的表格(即用于社交网络),我们需要2列:user_id, friend_id

表结构

user_id (medium_int)
friend_id (medium_int)

Primary Key -> (user_id, friend_id)

因此,主键(PK)是唯一的,并且通过创建复合PK,Innodb将在user_id, friend_id添加新记录时自动检查是否不存在重复项。这是预期的行为,例如,没有用户应拥有超过1条记录(关系链接)friend_id = 2

没有复合PK,我们可以使用代理键创建以下架构:

user_friend_id
user_id
friend_id

Primary Key -> (user_friend_id)

现在,无论何时添加新记录,我们都必须检查先前的记录是否与组合user_id, friend_id不存在。

这样,复合索引可以强制执行结构完整性。

启用对已过滤ID的排序

按帖子的时间(时间戳或日期时间)对一组记录进行排序是很常见的。通常,这意味着在给定的ID上发布。这是一个例子

表User_Wall_Posts(想想Facebook的墙贴)

user_id (medium_int)
timestamp (timestamp)
author_id (medium_int)
comment_post (text)

Primary Key -> (user_id, timestamp, author_id)

我们要查询和查找所有帖子,user_id = 10并按timestamp(日期)对评论帖子进行排序。

SQL查询

SELECT * FROM User_Wall_Posts WHERE user_id = 10 ORDER BY timestamp DES

复合PK使Mysql可以使用索引对结果进行过滤和排序。Mysql将不必使用临时文件或文件排序来获取结果。没有复合键,这将是不可能的,并且将导致效率非常低下的查询。

这样,组合键非常强大,并且比“我要搜索,column_a, column_b因此我将使用组合键”这个简单问题更适合。对于我当前的数据库架构,我拥有与单个键一样多的组合键。请不要忽略复合键的用途!


5

复合索引对于

  • 0个或更多“ =”子句,以及
  • 最多一个范围子句。

复合索引不能处理两个范围。我将在索引手册中对此进行进一步讨论。

查找最近的 -如果问题确实与优化有关

WHERE geolat BETWEEN ??? AND ???
  AND geolng BETWEEN ??? AND ???

那么没有索引可以真正处理这两个维度。

相反,必须“开箱即用”。如果一个维度是通过分区实现的,而另一个维度是通过仔细选择来实现的PRIMARY KEY,则对于非常大的经纬度查找表,一个维度可以获得明显更高的效率。我的博客博客详细介绍了如何在全球范围内实施“查找最近”。它包括代码。

PARTITIONs是纬度范围的条纹。该PRIMARY KEY故意经度启动,让有用的行很可能是在同一个块。存储例程编排杂乱的代码order by... limit...,以使目标周围的“正方形”得以实现,直到您拥有足够的咖啡店(或其他任何咖啡馆)为止。它还负责大圆的计算并处理日期线和极点。

更多

我写了另一个博客;它比较了进行lat / lng搜索的5种方式:http : //mysql.rjweb.org/doc.php/latlng#representation_choices (它引用了上面给出的链接作为5种 链接之一)。另一种方式是这样,并指出它们对于特定情况是最佳

INDEX(geolat, geolng),
INDEX(geolng, geolat)

也就是说,在两个索引中都有两个列,并且在geolat和geolng上没有单列索引很重要。


1

没有黑白,一种尺寸适合所有答案。

当您的查询工作量将从中受益时,您应该使用一个复合索引。

您需要分析您的查询工作负载以确定这一点。

当可以完全从该索引满足查询时,将使用复合索引。

UPDATE(响应对已发布问题的编辑):如果从表中选择*,则可以使用复合索引,但可能不使用。您需要运行EXPLAIN PLAN才能确定。


将复合索引用于地理位置数据(经度和纬度)是否有意义?
Teddy

1
这完全取决于针对该表执行的查询。
米奇·麦特

我已经更新了原始帖子,以包含执行的最常见查询。往上看。
Teddy

1

要进行空间搜索,您需要一种R-Tree算法,该算法可以非常快速地搜索地理区域。正是您需要的这项工作。

有些数据库内置了空间索引。快速的Google搜索显示MySQL 5拥有它们(我猜您正在使用MySQL的SQL)。


1

当您要优化group by子句时,复合索引可能很有用(请查看本文http://dev.mysql.com/doc/refman/5.0/en/group-by-optimization.html)。请注意:

使用GROUP BY索引的最重要前提是所有GROUP BY列均引用同一索引的属性,并且索引按顺序存储其键(例如,这是BTREE索引而不是HASH索引)


GROUP BY没有被提及。
里克·詹姆斯

没有提到哪里?:)我提到的文章中显然提到了它。它回答了以下问题:我何时应该在数据库中使用复合索引?使用复合索引对性能有何影响?为什么要使用复合索引?
亚历山大

更正: GROUP BYOP未提及。
里克·詹姆斯

当然,这就是答案-我们将在数据库中使用复合索引的情况之一。
亚历山大

0

我在@Mitch上,完全取决于您的查询。幸运的是,您可以随时创建和删除索引,并且可以在查询之前添加EXPLAIN关键字,以查看查询分析器是否使用索引。

如果您要查找确切的经 /纬对,则该索引可能会有意义。但是您可能会在特定位置的特定距离内寻找房屋,因此您的查询将类似于以下内容(请参阅参考资料):

select *, sqrt(  pow(h2.geolat - h1.geolat,  2) 
               + pow(h2.geolng - h1.geolng, 2) ) as distance
from homes h1, homes h2
where h1.home_id = 12345 and h2.home_id != h1.home_id
order by distance

并且该索引很可能根本没有帮助。对于地理空间查询,你需要像这样

更新:使用此查询:

SELECT * FROM homes
WHERE geolat BETWEEN ??? AND ???
AND geolng BETWEEN ??? AND ???

查询分析器可以单独使用geolat上的索引,也可以单独使用geolng上的索引,也可以同时使用两个索引。我认为它不会使用复合索引。但是,很容易在真实数据集上尝试这些排列,然后(a)查看EXPLAIN会告诉您什么,(b)测量查询实际花费的时间。


我只是想在方格内返回房屋。我了解空间,因此我不尝试计算距离。我只想返回正方形网格内的房屋,并希望其能够快速执行。因此,我想确保索引设置正确。有帮助吗?
Teddy
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.