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

时间:2009-12-01 02:47:07

标签: mysql indexing composite-index

  1. 我应该何时在数据库中使用复合索引?
  2. 使用a的性能分支是什么? 综合指数)?
  3. 我为什么要使用复合索引?
  4. 例如,我有一个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
    

    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的综合指数。我应该吗?

9 个答案:

答案 0 :(得分:95)

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

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 :(得分:48)

想象一下,您有以下三个问题:

查询I:

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),则只有查询I和查询II(使用composit索引的第一部分)才使用索引。在这种情况下,查询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在getot上进行搜索,因为它是由geolat索引的,但是可以通过geolat或“geolat AND geolng”进行搜索(因为geolng是二级索引)。

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

答案 2 :(得分: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
...

由于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,因为它使用八字节双精度数来存储坐标。如果我错了,请纠正我。

答案 3 :(得分:6)

复合索引

非常有用
  • 0或更多“=”条款,加上
  • 最多一个范围条款。

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

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

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

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

相反,人们必须“开箱即用”。如果一个维度是通过分区实现的,而另一个维度是通过仔细选择PRIMARY KEY来实现的,那么对于非常大的lat / lng查找表,可以获得明显更高的效率。我的latlng blog详细介绍了如何在地球上实现“最近找”。它包括代码。

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

答案 4 :(得分: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上是否存在重复项。这是预期的行为,因为例如,没有用户应该与friend_id = 2有超过1条记录(关系链接)。

如果没有复合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 QUERY

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

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

因此,复合键非常强大,并且比“我想搜索column_a, column_b的简单问题更适合所以我将使用复合键。对于我当前的数据库模式,我有尽可能多的复合键作为单个键。不要忽视复合键的使用!

答案 5 :(得分:1)

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

当您的查询工作负载受益于一个时,您应该使用复合索引。

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

当完全从该索引满足查询时,复合索引就会发挥作用。

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

答案 6 :(得分:1)

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

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

答案 7 :(得分:1)

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

  

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

答案 8 :(得分:0)

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

如果您要查找完全纬度/经度对,则该指数可能有意义。但是你可能会在某个特定地点的特定距离内找房子,所以你的查询看起来像这样(见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)测量查询真正需要的时间。