如何正确索引在具有多个连接的查询中使用的表

Posted

技术标签:

【中文标题】如何正确索引在具有多个连接的查询中使用的表【英文标题】:How to properly index tables used in a query with multiple joins 【发布时间】:2011-08-30 17:02:57 【问题描述】:

我正在尝试确定为下面的查询编制索引的最佳方式。

到目前为止,我已经在连接中使用的字段上创建了复合/分组索引,然后是我使用 where 子句过滤器的顺序。

或者,我应该在连接中使用的字段上创建单独的索引,并在 where/group by/order by 子句中使用的字段上创建单独的分组索引

  SELECT        [fields..]
  FROM          articles
  INNER JOIN    articles_to_geo 
    ON          articles_to_geo.article_id = articles.article_id 
  INNER JOIN    cities_whitelist 
    ON          cities_whitelist.city_id = articles_to_geo.whitelist_city_id
  INNER JOIN    cities 
    ON          cities.city_id = cities_whitelist.city_id
  INNER JOIN    articles_to_badges 
    ON          articles_to_badges.article_id = articles.article_id 
  INNER JOIN    badges 
    ON          badges.id = articles_to_badges.badge_id
  INNER JOIN    sites 
    ON          sites.id = articles.site_id
  WHERE         articles.expirydate > '2010-07-12'
  AND           articles.dateadded > '2010-08-11'
  AND           articles.status >= 6 

  AND           cities.city_id = 5794
  AND           cities.timezone = -7
  AND           cities_whitelist.published = 1      

  AND           articles_to_badges.badge_id IN (1,3,8,7)  

  ORDER BY      sites.sort_order";

例如,我的文章表有一个分组索引:

索引 1

article_id
site_id
expirydate
status
dateadded

或者我应该有 2 个索引吗?

index 1 //用于连接子句

article_id

index 2 //用于where/order by /group by子句

site_id
expirydate
status
dateadded

注意:我的其他表也有索引。

任何帮助将不胜感激

【问题讨论】:

取决于您使用的 RDBMS,因为它们管理索引的方式略有不同。 我认为需要了解 RDBMS。此外,为您已经设置的任何一个(或多个)提供解释计划(或任何您的 rdbms 所称的)可能会有所帮助。 【参考方案1】:

注意:我使用的是 SQL Server。如果您使用其他东西 - 这可能不适用。 另请注意:我将讨论索引以帮助访问表中的数据。覆盖索引是一个单独的主题,我不在这里讨论。

访问表时,有 3 种方法。

使用过滤条件。 使用已读取行中的关系标准。 阅读整张桌子!

我首先列出了所有表,包括过滤条件和关系条件。

articles

  articles.expirydate > 'somedate'
  articles.dateadded > 'somedate'
  articles.status >= someint

  articles.article_id <-> articles_to_geo.article_id
  articles.article_id <-> articles_to_badges.article_id
  articles.site_id <-> sites.id

articles_to_geo

  articles_to_geo.article_id <-> articles.article_id
  articles_to_geo.whitelist_city_id <-> cities_whitelist.city_id

cities_whitelist

  cities_whitelist.published = someint

  cities_whitelist.city_id <-> articles_to_geo.whitelist_city_id
  cities_whiltelist.city_id <-> cities.city_id

cities

  cities.city_id <-> cities_whiltelist.city_id

articles_to_badges

  articles_to_badges.badge_id in (some ids)

  articles_to_badges.article_id <-> articles.article_id
  article_to_badges.badge_id <-> badges.id

badges

  badges.id <-> article_to_badges.badge_id

sites

  sites.id <-> articles.site_id

解决这个问题的最笨拙的方法是简单地在每个表上创建一个支持每个关系和过滤标准的索引......然后让优化器选择它想要使用的索引。这种方法非常适合 IO 性能,而且操作简单……但在未使用的索引中会占用大量空间。

下一个最佳方法是在打开这些选项的情况下运行查询:

SET STATISTICS IO ON
SET STATISTICS TIME ON

如果一组特定的表使用更多的 IO,则可以将索引工作集中在它们上。要做到这一点,依赖于表访问顺序的优化器计划已经相当不错了。


如果优化器由于缺少索引而根本无法制定一个好的计划,我要做的就是确定我希望访问表的顺序,然后添加支持这些访问的索引。

注意:访问的第一个表没有使用关系标准的选项,因为尚未读取任何记录。第一个表必须通过过滤条件访问或读取整个表。

一种可能的顺序是查询中的顺序。这种方法可能非常糟糕,因为我们的文章过滤标准基于 3 个不同的范围。可能有数以千计的文章符合该标准,并且很难制定一个索引来支持这些范围。

Articles (Filter)
  Articles_to_Geo (Relational by Article_Id)
    Cities_WhiteList (Relational by City_Id) (Filter)
    Cities (Relational by City_Id) (Filter)
  Articles_to_Badges (Relational by Article_Id) (Filter)
    Badges (Relational by Badge_Id)
  Sites (Relational by Article_Id)

另一个可能的顺序是城市优先。城市标准很容易索引,可能只有 1 行!查找某个城市的文章然后按日期过滤所读取的行数应该少于查找日期的文章然后过滤到该城市的行数。

Cities (Filter)
  Cities_WhiteList (Relational by City_Id) (Filter)
  Articles_to_Geo (Relational by City_Id)
    Articles (Relational by Article_Id) (Filter)
      Articles_to_Badges (Relational by Article_Id) (Filter)
        Badges (Relational by Badge_Id)
      Sites (Relational by Article_Id)

第三种方法可能是徽章优先。如果文章很少积累徽章并且徽章不多,这将是最好的。

Badges (Read the Whole Table)
  Articles_to_Badges (Relational by Badge_Id) (Filter)
    Articles (Relational by Article_Id) (Filter)
      Articles_to_Geo (Relational by Article_Id)
        Cities_WhiteList (Relational by City_Id) (Filter)
        Cities (Relational by City_Id) (Filter)
    Sites (Relational by Article_Id)

【讨论】:

【参考方案2】:

我建议阅读以下内容:http://hackmysql.com/case4

它很好地解释了何时/什么索引。

首先我会为这些创建索引:

    articles_to_geo.article_id cities_whitelist.city_id cities.city_id articles_to_badges.article_id articles_to_badges.badge_id 徽章.id sites.id

如果没有上述内容,您的连接 + IN() 将永远持续

【讨论】:

【参考方案3】:

编辑:我从文章索引中删除了 article_id 字段

在过去,RDBMS 系统无法在一张表上组合 B-Tree 索引。看到这篇文章http://use-the-index-luke.com/sql/where-clause/searching-for-ranges/index-merge-performance。这意味着例如如果您对该查询中使用的所有文章列都有单独的索引,那么只会使用这些索引中的一个。

仅基于此查询,您应该具有以下索引:

文章

site_id
expirydate
status
dateadded

articles_to_geo

article_id

城市白名单

city_id

城市 网站加入 sites.id = articles.site_id 在这里我想 id 是网站上的主键,因此不需要在 cities.city_idcities.timezone 上添加其他索引,因为它们无论如何都会成为过滤谓词的一部分

articles_to_badges

article_id
badge_id (or this could be a second index of type Bitmap, refer to the article above)

徽章 还加入了主键,如果您在 id 字段上有唯一索引,则不需要额外的索引

关于文章索引的说明: 索引中字段的顺序与 where 子句中出现的字段顺序无关。 如果你保持这个顺序,那么索引可以用于你指定的所有查询

和 site_id site_id 和到期日期 等

但这不能用在那些你只指定的查询中

有效期 有效期和状态 等

【讨论】:

那么对于文章表,您是说我应该在这些字段上创建组索引吗?还是应该为每个字段创建单独的索引? articles 表上的组索引可能会提高您指定的此查询的性能。如果您创建单独的索引,则此查询将只使用其中一个(可能是article_id)。我不确定组索引的性能差异会有多大,但可能会很大(这取决于文章表中日期的性质) 原来mysql之所以选择忽略articles表上的组索引,是因为索引太大(5个字段),使用单个索引会更快。

以上是关于如何正确索引在具有多个连接的查询中使用的表的主要内容,如果未能解决你的问题,请参考以下文章

如何在单个查询中使用索引进行多表连接?

您如何优化这个复杂的 sql 查询,然后选择正确的表索引

mysql在具有1亿行的表上创建索引

从 MySQL 中具有不同列的表的多个连接结果中删除重复项

如何使用 Nhibernate 从连接两个具有所有 id 的表中选择只有一个不同列的多个列是 UNIQUEIDENTIFIER

使用条件连接语句时执行多个全索引扫描