【问题标题】:When should I use a composite index?什么时候应该使用复合索引?
【发布时间】:2010-12-21 21:00:01
【问题描述】:
  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 和 geolng 的复合索引。我应该是吗?

【问题讨论】:

    标签: mysql indexing composite-index


    【解决方案1】:

    没有黑白之分,一刀切。

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

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

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

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

    【讨论】:

    • 对地理位置数据(纬度和经度)使用复合索引是否有意义?
    • 这完全取决于对该表进行的查询。
    • 我已经更新了我的原始帖子以包含最常见的查询。见上文。
    • @MitchWheat - “覆盖”,而不是“复合”是指可以完全从索引中满足查询。 (“覆盖”索引通常是“复合的”。)
    • @Teddy - 请参阅mysql.rjweb.org/doc.php/find_nearest_in_mysql#bounding_box 了解地理位置。
    【解决方案2】:

    复合索引

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

    复合索引不能处理 两个 范围。我会在index cookbook 中进一步讨论这个问题。

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

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

    那么 no 索引确实可以处理两个维度。

    相反,人们必须“跳出框框思考”。如果一个维度是通过分区实现的,而另一个维度是通过仔细挑选PRIMARY KEY 来实现的,那么对于非常大的 lat/lng 查找表来说,效率会显着提高。我的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 上有单列索引很重要。

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

      • GROUP BY 未被提及。
      • 哪里没提到? :) 我提到的文章中显然提到了这一点。它回答了被问到的问题:我什么时候应该在数据库中使用复合索引?使用复合索引会对性能产生什么影响)?为什么要使用复合索引?
      • 更正:GROUP BY 没有被 OP 提及。
      • 当然,这就是答案 - 我们将在数据库中使用复合索引的情况之一。
      【解决方案4】:

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

      • 强制结构完整性
      • 对过滤后的 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 上发布。这是一个例子

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

      【讨论】:

      • PRIMARY KEY 由于是UNIQUE 而强制执行完整性;复合是次要的。
      【解决方案5】:

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

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

      【讨论】:

        【解决方案6】:

        对于复合索引的作用可能存在误解。许多人认为只要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 提供了spatial extension 支持。空间点存储在单个列中,而不是两个单独的 lat lng 列中。空间索引可以应用于这样的列。但是,根据我的个人经验,效率可能被高估了。可能是空间索引并没有解决二维问题,而只是使用 R-Trees with quadratic splitting 来加快搜索速度。

        权衡是空间点consumes much more memory,因为它使用八字节双精度数字来存储坐标。如果我错了,请纠正我。

        【讨论】:

          【解决方案7】:

          假设您有以下三个查询:

          查询一:

          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 需要全表搜索。

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

          来自MySQL Reference Manual page

          多列索引可以是 被认为是一个排序数组,包含 创建的值 连接的值 索引列

          如果您对 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 是二级索引)。

          另外,请查看How MySQL Uses Indexes 手册部分。

          【讨论】:

          • 实际上,我没有任何疑问。我的查询列在原始帖子中。我的查询是在方形网格内返回房屋。我知道空间,我不想计算距离。我只是想知道在尝试显示特定地理网格(例如社区/城市/县)内的所有房屋时使用复合索引是否有意义
          • Eyazici,我已经更新了我的原始帖子(更新 2)。这是我的实际查询。我的实际数据库架构。以及 EXPLAIN 命令返回的内容。所以,有了这些信息——我应该使用复合索引吗?我还不清楚。提前致谢
          • @"实际上,我没有任何疑问。"。其实你有,我用简单的 WHERE 条件来解释基本逻辑。当在列上使用条件(即 WHERE)时,MySQL 会尽可能尝试使用索引。 “x BETWEEN a AND b”类似于“x>a AND x应该为您的场景使用每列单独的索引。
          • 我不明白。当我总是执行包含两列的查询时,为什么要为 geolat 和 geolng 使用单独的索引
          • 没有。当遇到“范围”时(如BETWEEN),不再考虑索引的其他字段!所以综合指数也好不到哪里去。
          【解决方案8】:

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

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

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

          【讨论】:

          • 马克,我已经更新了我的原始帖子(更新 2)。这是我的实际查询。我的实际数据库架构。以及 EXPLAIN 命令返回的内容。所以,有了这些信息——我应该使用复合索引吗?我还不清楚。提前致谢。
          • 马克,你答案中的复合索引是否满足 index(column_C)?
          • -1 因为复合索引WHERE geolat BETWEEN ??? AND ??? AND geolng BETWEEN ??? AND ???有帮助。它将在第一个字段后停止。 “问题溢出”的答案解释了原因。
          • 我真正想知道的是:与每列上的单独索引相比,组合有什么好处?
          • @felwithe MySQL 只能为查询中的每个表使用一个索引(有例外情况。例如,索引合并)。理想情况下,查询中的表必须对所有 where 子句、表连接、group-by 和 order-by 使用单个索引。因此,每列上的单独索引可能并不总是有效,但复合索引可以做到这一点。
          【解决方案9】:

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

          如果您要查找 exact 纬度/经度对,则此索引可能有意义。但是您可能会在某个特定地点的一定距离内寻找房屋,因此您的查询将如下所示(请参阅source):

          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
          

          而且索引很可能根本没有帮助。对于地理空间查询,您需要类似this

          更新:使用此查询:

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

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

          【讨论】:

          • 我只是想在一个方格内回家。我知道空间,所以我不想计算距离。我只是想在方格内返回家园,并希望它能够快速执行。因此,我想确保我的索引设置正确。这有帮助吗?
          猜你喜欢
          • 2011-03-19
          • 2010-09-08
          • 2011-02-22
          • 1970-01-01
          • 2011-03-23
          • 2011-01-16
          • 2019-12-26
          相关资源
          最近更新 更多