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

  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  ;

我对 geolatgeolng都使用复合索引是否有意义,例如:

我代替:

  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 和 geolong 的复合索引。我应该害怕吗?

80331 次浏览

没有黑与白,一个尺寸适合所有的答案。

如果查询工作负载可以从中受益,则应使用复合(或多列)索引。

您需要对查询工作负载进行概要分析,以确定这一点。

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

更新(针对发布的问题进行编辑) : 如果您从表中选择 * ,则可以使用复合索引,但不能使用。您将需要运行 解释计划以确保。

假设您有以下三个查询:

问题一:

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 中,如果有复合索引(geolatgeolng) ,则只有查询 I 和查询 II (使用复合索引的第一部分)使用索引。在这种情况下,查询 III 需要完整的表搜索。

在手册的 多列索引部分,它清楚地解释了多列索引是如何工作的,所以我不想重新输入手册。

来自 MySQL 参考手册页:

多列索引可以是 被认为是包含 创建的 < strong > 值 值的连接 索引列 .

如果对 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)。前两个索引分别生成,第三个索引是复合索引。正如你所看到的,你可以通过 geolat 进行搜索,但是也可以通过 geolat 或者“ geolat AND geolng”进行搜索(因为 geolg 是二级索引)。

另外,看一下 MySQL 如何使用索引手动部分。

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

如果您将查找 一模一样 lat/long 对,这个索引可能有意义。但是你可能会在一个特定的地方寻找一定距离内的房子,所以你的查询会看起来像这样(见 来源) :

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)测量查询真正花费的时间。

当使用从中受益的查询时,应该使用复合索引。一个综合指数,看起来像这样:

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

在您的原始示例中,针对两个维度的复合索引最有利于查询同时查询两个维度或最左边的维度,而不是查询最右边的维度。如果您总是在查询两个维度,那么复合索引就是一种方法,其实哪个是第一个(很可能)并不重要。

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

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

对于复合指数的作用,人们可能存在误解。许多人认为,只要 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的值几乎不会重复,所以 geolatgeolng的综合指数应该是这样的:

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 树

折衷之处在于,空间点 消耗更多的内存使用8字节的双精度数字来存储坐标。如果我说错了请纠正我。

综合指数非常强大,因为它们:

  • 执行结构完整性
  • 在筛选标识上启用排序

加强结构完整性

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

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

为了创建一个朋友的表格(比如社交网络) ,我们需要两列: 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上是否存在重复记录。这是预期的行为,因为没有用户应该有一个以上的记录(关系链接) ,例如 friend_id = 2

如果没有组合 PK,我们可以使用代理键创建这个模式:

user_friend_id
user_id
friend_id


Primary Key -> (user_friend_id)

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

因此,复合索引可以强制结构完整性。

启用筛选标识上的排序

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

Table 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,所以我将使用复合键”这个简单问题更适合。对于我当前的数据库模式,我拥有的复合键与单个键一样多。不要忽略组合键的使用!

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

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

复合索引 对于

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

复合索引无法处理 范围。我将在 < em > 索引食谱 中进一步讨论这个问题。

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

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

那么 没有指数真的可以同时处理这两个维度。

相反,人们必须“跳出思维定势”。如果一个维度是通过分区实现的,而另一个维度是通过仔细选择 PRIMARY KEY实现的,那么对于非常大的 lat/lng 查找表来说,可以获得明显更高的效率。我的 < em > Latlng blog 详细介绍了如何在全球范围内实现“查找最近”。里面有代码。

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 上包含单列索引非常重要。