在我们进入过早优化模式之前,查看以下查询模板可能会很有用。如果不出意外,这可以用作衡量可能优化效果的基准。
SELECT T.Tagid, TagInfo.TagName, COUNT(*)
FROM Items I
JOIN Tags TagInfo ON TagInfo.TagId = T.TagId
JOIN ItemTagMap T ON I.ItemId = T.ItemId
--JOIN ItemTagMap T1 ON I.ItemId = T1.ItemId
WHERE I.ItemId IN
(
SELECT ItemId
FROM Items
WHERE -- Some typical initial search criteria
Title LIKE 'Bug Report%' -- Or some fulltext filter instead...
AND ItemDate > '02/22/2008'
AND Status = 'C'
)
--AND T1.TagId = 'MySql'
GROUP BY T.TagId, TagInfo.TagName
ORDER BY COUNT(*) DESC
子查询是“驱动查询”,即与最终用户的初始条件相对应的查询。 (有关此查询的详细信息,请参阅下文,需要多次可能适合整体优化流程)
Commented is the JOIN on T1 (and possibly T2, T3, when several tags are selected), and, with the WHERE clause, the associated criteria.当用户选择特定标签时需要这些,无论是作为初始搜索的一部分还是通过细化。 (将这些连接和 where 子句放在子查询中可能更有效;下面会详细介绍)
>
讨论...
两个不同的目的需要“驱动查询”或其变体:
-
1 提供枚举所有关联标签所需的 ItemId 的完整列表。
-
2 提供前 N 个 ItemId 值(N 为显示页面大小),用于在 Item 表中查找 Item 详细信息。
请注意,不需要对完整列表进行排序(或者它可能会受益于以不同的顺序排序),第二个列表需要根据用户的选择(例如按日期、降序或按标题)进行排序,按字母升序)。另请注意,如果需要任何排序顺序,查询的成本将意味着处理完整列表(SQL 本身的奇怪优化和/或一些非规范化,SQL 需要“查看”该列表中的最后一条记录,如果它们属于顶部,则按排序)。
后一个事实有利于为这两个目的使用相同的查询,相应的列表可以存储在临时表中。一般流程是快速查找前 N 项记录及其详细信息,并立即将其返回给应用程序。然后应用程序可以获取 ajax-fashion 的标签列表以进行细化。该列表将使用与上述类似的查询生成,其中子查询被“select * from temporaryTable”替换。 SQL 优化器决定对这个列表进行排序(在某些情况下)的可能性很大,让我们让它这样做,而不是再次猜测它并显式排序。
要考虑的另一点是可能将 ItemTagMap 表上的连接带入“驱动查询”,而不是如上所示。这样做可能是最好的选择,既是为了提高性能,又是因为它会为 #2 目的(显示一页项目)生成正确的列表。
上述查询/流程可能会很好地扩展,即使在相对适中的硬件上也是如此;暂时进入 1/2 百万+ 项,持续的用户搜索可能高达每秒 10 次。关键因素之一是初始搜索条件的选择性。
优化思路
- [取决于典型的搜索案例和数据统计] 通过将某些项目的字段引入(实际上是复制)ItemTagMap 表来进行非规范化可能是有意义的。特别是短字段可能会在那里受到“欢迎”。
- 随着数据以百万以上的方式增长,我们可以利用各种技巧来利用某些标签通常具有很强的相关性(例如:在 SO 中,PHP 经常附带 MySql,顺便说一句,通常没有充分的理由......)。例如,“多标签”TagIds 的引入可能会使输入逻辑更加复杂,但也可以显着减小 Map 的大小。
-- '不用说了! --
应根据实际需求和有效的数据统计概况选择合适的架构和优化...