优化类别过滤器

Posted

技术标签:

【中文标题】优化类别过滤器【英文标题】:Optimizing a category filter 【发布时间】:2011-09-01 15:44:45 【问题描述】:

This recent question 让我考虑。

假设我们希望创建一个引用大量音轨的数据库,包括它们的发布日期和可从中下载音轨的世界位置列表。

我们希望优化的请求是:

给我从位置 A 下载的 10 首最新曲目。 请提供可从位置 A 或 B 下载的 10 首最新曲目。 请提供可从位置 A 和 B 下载的 10 首最新曲目。

如何构建该数据库?我很难想出一个简单的解决方案,不需要阅读至少一个位置的所有曲目...

【问题讨论】:

您是否受限于特定的 SQL 平台?例如MS SQL Server,甲骨文? 我的背景是 mysql,但我也对特定于平台的解决方案感到好奇。 【参考方案1】:

要优化这些查询,您需要稍微去规范化数据。

例如,您可能有一个 track 表,其中包含轨道的 idnamerelease date,还有一个 map_location_to_track 表,其中描述了可以从何处下载这些轨道。要回答“位置 A 的 10 个最新曲目”,您需要从 map_location_to_track 获取位置 A 的所有曲目,然后将它们加入 track 表以通过 release date 订购它们,然后选择前 10 个。

如果所有数据都在一个表中,则可以避免排序步骤。比如……

CREATE TABLE map_location_to_track (
  location_id   INT,
  track_id      INT,
  release_date  DATETIME,
  PRIMARY KEY (location_id, release_date, track_id)
)

SELECT * FROM map_location_to_track
WHERE location_id = A
ORDER BY release_date DESC LIMIT 10

将 location_id 作为主键中的第一个条目可确保 WHERE 子句只是一个索引查找。那么数据就不需要重新排序了,我们已经按照主键排序了,而是选择最后的10条记录。

您确实可能仍然加入track 表以获取名称、价格等,但您现在只需为 10 条记录执行此操作,而不是该位置的所有记录。

要解决“位置 A OR B”的相同查询,有几个选项可以根据您使用的 RDBMS 执行不同的操作。

第一个很简单,虽然有些 RDBMS 不能很好地配合 IN...

SELECT track_id, release_date FROM map_location_to_track
WHERE location_id IN (A, B)
GROUP BY track_id, release_date
ORDER BY release_date DESC LIMIT 10

下一个选项几乎相同,但仍然有一些 RDBMS 不能很好地将 OR 逻辑应用于 INDEX。

SELECT track_id, release_date FROM map_location_to_track
WHERE location_id = A or location_id = B
GROUP BY track_id, release_date
ORDER BY release_date DESC LIMIT 10

在任何一种情况下,用于将记录列表合理化到 10 条的算法对您都是隐藏的。试一试看看;该索引仍然可用,因此可以执行此操作。

另一种方法是在您的 SQL 语句中明确确定部分方法...

SELECT
  *
FROM
(
  SELECT track_id, release_date FROM map_location_to_track
  WHERE location_id = A
  ORDER BY release_date DESC LIMIT 10

  UNION

  SELECT track_id, release_date FROM map_location_to_track
  WHERE location_id = B
  ORDER BY release_date DESC LIMIT 10
)
  AS data
ORDER BY
  release_date DESC
LIMIT 10

-- NOTE: This is a UNION and not a UNION ALL
--       The same track can be available in both locations, but should only count once
--       It's in place of the GROUP BY in the previous 2 examples

优化器仍然有可能意识到这两个联合数据集是有序的,因此可以非常快速地进行外部排序。但是,即使没有,订购 20 件商品也很快。更重要的是,这是一个固定开销:每个位置是否有 10 亿条轨道都没有关系,我们只是合并两个 10 条的列表。

最难优化的是 AND 条件,但即便如此,“TOP 10”约束的存在也有助于创造奇迹。

将 HAVING 子句添加到基于 INOR 的方法可以解决此问题,但同样,根据您的 RDBMS,可能会运行得不太理想。

SELECT track_id, release_date FROM map_location_to_track
WHERE location_id = A or location_id = B
GROUP BY track_id, release_date
HAVING COUNT(*) = 2
ORDER BY release_date DESC LIMIT 10

另一种方法是尝试“两个查询”的方法......

SELECT
  location_a.*
FROM
(
  SELECT track_id, release_date FROM map_location_to_track
  WHERE location_id = A
)
  AS location_a
INNER JOIN  
(
  SELECT track_id, release_date FROM map_location_to_track
  WHERE location_id = B
)
  AS location_b
    ON  location_a.release_date = location_b.release_date
    AND location_a.track_id     = location_b.track_id
ORDER BY
  location_a.release_date DESC
LIMIT 10

这次我们不能将两个子查询限制为只有 10 条记录;据我们所知,最近的 10 个位置 a 并没有出现在位置 b 根本。主键再次拯救了我们。这两个数据集是按发布日期组织的,RDBMScan 只是从每个数据集的顶部记录开始,然后将两者合并,直到它有 10 条记录,然后停止。

注意:因为release_date 在主键中,并且在track_id 之前,所以应该确保在连接中使用它。

根据 RDBMS,您甚至不需要子查询。您也许能够在不更改 RDBMS 计划的情况下自行加入表...

SELECT
  location_a.*
FROM
  map_location_to_track AS location_a
INNER JOIN  
  map_location_to_track AS location_b
    ON  location_a.release_date = location_b.release_date
    AND location_a.track_id     = location_b.track_id
WHERE
      location_a.location_id = A
  AND location_b.location_id = B
ORDER BY
  location_a.release_date DESC
LIMIT 10

总而言之,三件事的结合使这非常有效: - 对数据进行部分去规范化,以确保它符合我们的需求 - 知道我们只需要前 10 个结果 - 知道我们最多只处理 2 个地点

存在可以针对任意数量的记录和任意数量的位置进行优化的变体,但这些变体的性能明显低于此问题中所述的问题。

【讨论】:

希望有一天我能博学多才,写出如此清晰完整的答案。 如果您不想对数据进行非规范化,请按照此答案的建议执行操作,但要基于连接的物化视图。您可以索引物化视图(在 oracle 中)。我猜其他平台也有类似的功能。【参考方案2】:

在经典的关系模式中,您将在轨道和位置之间建立多对多关系以避免冗余:

CREATE TABLE tracks (
  id   INT,
  ...
  release_date  DATETIME,
  PRIMARY KEY (id)
)

CREATE TABLE locations (
  id   INT,
  ...
  PRIMARY KEY (id)
)

CREATE TABLE tracks_locations (
  location_id   INT,
  track_id      INT,
  ...
  PRIMARY KEY (location_id, track_id)
)

SELECT tracks.* FROM tracks_locations LEFT JOIN tracks ON tracks.id = tracks_locations.location_id
WHERE tracks_locations.location_id = A
ORDER BY tracks.release_date DESC LIMIT 10

您可以按位置使用表分区来修改该架构。问题在于它取决于实现问题或使用限制。例如,在 MySQL 中的 AFAIK,您不能在分区表中拥有外键。为了解决这个问题,您还可以拥有一组表(称为“手动分区”),例如tracks_by_location_#,其中# 是已知位置的ID。这些表可以存储过滤结果并使用触发器创建/更新/删除。

【讨论】:

以上是关于优化类别过滤器的主要内容,如果未能解决你的问题,请参考以下文章

php Сustom类别过滤器解析URL类别过滤器按自定义过滤器按属性自定义过滤器自定义排序

WordPress |帖子查询 |查询帖子类别以创建子类别过滤器并将其应用于我的函数文件中的 Ajax 过滤器

如何使magento过滤器像类别一样工作

Magento 类别页面未包含属性过滤器中的所有产品

php 类别过滤器分类多过滤器多过滤器多过滤器

Youtube“类别过滤器无效”API 错误