153
  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对我来说对and使用复合索引是否有意义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

解释返回:

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 的复合索引。我可以做?

4

9 回答 9

124

当您使用从中受益的查询时,您应该使用复合索引。如下所示的复合索引:

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 是如何丢失的。

在您的原始示例中,二维的复合索引将主要有利于查询两个维度或最左侧维度本身的查询,而不是最右侧维度本身的查询。如果您总是查询两个维度,那么复合索引是可行的方法,哪个是第一个(最有可能)并不重要。

于 2009-12-01T03:31:43.747 回答
64

假设您有以下三个查询:

查询一:

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

查询二:

SELECT * FROM homes WHERE `geolat`=42.9

查询三:

SELECT * FROM homes WHERE `geolng`=36.4

如果每列有单独的索引,则所有三个查询都使用索引。在 MySQL 中,如果您有复合索引 ( geolat, geolng),则只有查询 I 和查询 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)。前两个索引是单独生成的,第三个索引是复合的。如您所见,您可以基于 geolng 在复合索引上进行搜索,因为它是由 geolat 索引的,但是可以通过 geolat 或“geolat AND geolng”进行搜索(因为 geolng 是二级索引)。

另外,请查看MySQL 如何使用索引手册部分。

于 2009-12-01T03:09:21.700 回答
21

对于复合索引的作用可能存在误解。许多人认为只要where子句覆盖索引列,复合索引就可以用来优化搜索查询,在你的情况下geolatgeolng. 让我们更深入地研究:

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

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-Trees来加速搜索。

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

于 2011-12-24T03:54:28.750 回答
8

复合索引可用于

  • 0 个或多个“=”子句,加上
  • 最多一个范围子句。

复合索引不能处理两个范围。我将在我的索引食谱中进一步讨论这一点。

查找最近的——如果问题真的是关于优化

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

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

相反,一个人必须“跳出框框思考”。如果一个维度是通过分区实现的,而另一个维度是通过仔细挑选来实现的PRIMARY KEY,那么对于非常大的 lat/lng 查找表来说,效率会显着提高。我的latlng 博客详细介绍了如何在全球范围内实现“查找最近”。它包括代码。

PARTITIONs是纬度范围的条纹。故意以经度开头,PRIMARY KEY以便有用的行可能位于同一块中。存储例程编排了杂乱的代码,用于order by... limit...在目标周围增加“正方形”,直到您有足够的咖啡店(或其他任何东西)。它还负责大圆计算并处理日期变更线和极点。

更多的

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

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

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

于 2015-11-28T01:14:36.257 回答
6

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

  • 强制结构完整性
  • 启用对 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所以我将使用组合键”的简单问题更适合。对于我当前的数据库模式,我的组合键与单个键一样多。不要忽视复合键的用途!

于 2012-12-29T15:22:29.057 回答
1

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

一些数据库内置了空间索引。快速的谷歌搜索显示 MySQL 5 有它们(查看你的 SQL,我猜你正在使用 MySQL)。

于 2009-12-01T04:46:40.147 回答
1

没有黑白之分,一刀切的答案。

当您的查询工作负载将从其中受益时,您应该使用复合(或多列)索引。

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

当可以完全从该索引满足查询时,复合索引就会发挥作用:这意味着查询所需的所有列都在索引中(覆盖)。

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

于 2009-12-01T02:52:39.300 回答
1

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

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

于 2013-12-30T11:19:55.480 回答
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) 测量查询实际花费的时间。

于 2009-12-01T03:13:57.183 回答