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

Ted*_*ddy 122 mysql indexing composite-index

  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  ;
Run Code Online (Sandbox Code Playgroud)

是否有意义,我使用的复合指数都geolatgeolng,这样的:

我替换:

  KEY `geolat` (`geolat`),
  KEY `geolng` (`geolng`),
Run Code Online (Sandbox Code Playgroud)

有:

KEY `geolat_geolng` (`geolat`, `geolng`)
Run Code Online (Sandbox Code Playgroud)

如果是这样:

  • 为什么?
  • 使用复合索引的性能分支是什么?

更新:

由于很多人都声明它完全依赖于我执行的查询,因此下面是最常见的查询:

SELECT * FROM homes
WHERE geolat BETWEEN ??? AND ???
AND geolng BETWEEN ??? AND ???
Run Code Online (Sandbox Code Playgroud)

更新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 ;
Run Code Online (Sandbox Code Playgroud)

使用以下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
Run Code Online (Sandbox Code Playgroud)

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  
Run Code Online (Sandbox Code Playgroud)

我不太明白如何阅读EXPLAIN命令.这看起来好还是坏.现在,我没有使用geolat和geolng的综合指数.我可以做?

Mar*_*las 100

在使用受益的查询时,应使用复合索引.一个如下所示的复合索引:

index( column_A, column_B, column_C )
Run Code Online (Sandbox Code Playgroud)

将使用这些字段进行连接,过滤和有时选择的查询受益.它还将有益于使用该组合中最左侧列的子集的查询.所以上面的索引也将满足需要的查询

index( column_A, column_B, column_C )
index( column_A, column_B )
index( column_A )
Run Code Online (Sandbox Code Playgroud)

但它不会(至少不直接,也许它可以帮助部分,如果没有更好的索引)帮助查询需要

index( column_A, column_C )
Run Code Online (Sandbox Code Playgroud)

注意column_B是如何丢失的.

在您的原始示例中,两个维度的复合索引将主要有益于在两个维度或最左侧维度上查询的查询,而不是最右侧维度.如果你总是在查询两个维度,那么复合索引是要走的路,首先(最有可能)并不重要.

  • 马克,我已经更新了我的原始帖子(更新 2)。这是我的实际查询。我的实际数据库架构。以及 EXPLAIN 命令返回的内容。因此,有了这些信息 - 我应该使用复合索引吗?我还是不清楚。提前致谢。 (2认同)
  • @felwithe MySQL 只能对查询中的每个表使用一个索引(有例外。例如,索引合并)。理想情况下,这意味着查询中的表必须对所有 where 子句、表连接、分组依据和排序依据使用单个索引。因此,每列上的单独索引可能并不总是有效,但复合索引可以发挥神奇作用。 (2认同)

Emr*_*ici 50

想象一下,您有以下三个查询:

查询I:

SELECT * FROM homes WHERE `geolat`=42.9 AND `geolng`=36.4
Run Code Online (Sandbox Code Playgroud)

查询II:

SELECT * FROM homes WHERE `geolat`=42.9
Run Code Online (Sandbox Code Playgroud)

问题III:

SELECT * FROM homes WHERE `geolng`=36.4
Run Code Online (Sandbox Code Playgroud)

如果每列有单独的索引,则所有三个查询都使用索引.在MySQL中,如果您有复合索引(geolat,geolng),则只有查询I和查询II(使用composit索引的第一部分)使用索引.在这种情况下,查询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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

RRN是相对记录号(简化,你可以说ID).前两个索引生成单独的,第三个索引是复合的.正如你所看到的,你可以根据geolng在geolat上进行搜索,因为它是由geolat索引的,但是可以通过geolat或"geolat AND geolng"进行搜索(因为geolng是二级索引).

另外,看看MySQL如何使用索引手册部分.

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

Que*_*low 18

关于复合索引的作用可能存在误解.很多人认为,综合指数可以用来只要来优化搜索查询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
...
Run Code Online (Sandbox Code Playgroud)

因为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
...
Run Code Online (Sandbox Code Playgroud)

因此复合索引的第二列基本没用!使用复合索引的查询速度可能与geolat列中的索引类似.

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

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


Ric*_*mes 6

复合索引非常有用

  • 0或更多"="子句,加上
  • 最多一个范围条款.

复合索引无法处理两个范围.我在索引食谱中进一步讨论了这一点.

查找最近的 - 如果问题实际上是关于优化

WHERE geolat BETWEEN ??? AND ???
  AND geolng BETWEEN ??? AND ???
Run Code Online (Sandbox Code Playgroud)

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

相反,人们必须"开箱即用".如果通过分区实现一个维度而通过仔细选择实现另一个维度PRIMARY KEY,那么对于非常大的lat/lng查找表,可以获得明显更好的效率.我的latlng博客详细介绍了如何在全球范围内实现"最近找到".它包括代码.

PARTITIONs是纬度范围的条纹.该PRIMARY KEY故意经度启动,让有用的行很可能是在同一个块.一个存储的例程编排了杂乱的代码,用于做order by... limit...和围绕目标增长"正方形",直到你有足够的咖啡店(或其他).它还负责大圆计算和处理日期线和极点.


Pro*_*ist 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)
Run Code Online (Sandbox Code Playgroud)

因此,主键(PK)是唯一的,通过创建复合PK,Innodb将自动检查user_id, friend_id添加新记录时是否存在重复项.这是预期的行为,因为没有用户应该具有多于1个记录(关系链接)friend_id = 2.

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

user_friend_id
user_id
friend_id

Primary Key -> (user_friend_id)
Run Code Online (Sandbox Code Playgroud)

现在,每当添加新记录时,我们将不得不检查具有该组合的先前记录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)
Run Code Online (Sandbox Code Playgroud)

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

SQL QUERY

SELECT * FROM User_Wall_Posts WHERE user_id = 10 ORDER BY timestamp DES
Run Code Online (Sandbox Code Playgroud)

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

因此,复合键非常强大,并且比"我想要搜索的简单问题更适合column_a, column_b所以我将使用复合键.对于我当前的数据库模式,我有与单键一样多的复合键.不要忽视复合键的用途!


Mit*_*eat 1

没有非黑即白的答案,一刀切的答案。

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

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

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

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