This recent question让我考虑优化类别过滤器。
假设我们希望创建一个引用大量音轨的数据库,其发布日期和可从中下载音轨的世界位置列表。
我们希望优化的请求是:
如何构建该数据库?我很难想出一个简单的解决方案,不需要通过所有轨道读取至少一个位置......
答案 0 :(得分:7)
要优化这些查询,您需要稍微对数据进行反规范化。
例如,您可能有一个track
表,其中包含曲目的id
,name
和release 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逻辑应用于INDEXes。
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个曲目。
最难以优化的是AND条件,但即使这样,“TOP 10”约束的存在也可以帮助创造奇迹。
向基于IN
或OR
的方法添加HAVING子句可以解决这个问题,但是,再次,根据您的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 的所有中。然而,主键再次救了我们。这两个数据集按发布日期进行组织,RDBMS可以从每个集合的最高记录开始,然后将两者合并,直到它有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个地点
有些变体可以优化到任意数量的记录和任意数量的位置,但这些变量的性能远远低于此问题中所述的问题。
答案 1 :(得分:0)
在经典的关系模式中,您将在曲目和位置之间建立多对多的关系,以避免冗余:
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。这些表可以存储过滤结果,并使用触发器创建/更新/删除。