我正在用PHP / MySQL编写一个网站,我想实现一个类似于stackoverflow标记引擎。我在DB中有3个相关表: 1.项目 2.标签 3. ItemTagMap(将标签映射到项目,n:n映射)
现在,在搜索页面上,我想显示整个搜索结果(不仅仅是当前页面)的所有标签的不同列表,以便用户可以通过添加/删除该标签列表中的标签来“优化”他们的搜索。
问题是,这是对数据库的一个非常繁重的查询,并且可能有大量的搜索请求导致不同的结果集,从而导致不同的标记集。
有谁知道如何有效地实现这一目标?
答案 0 :(得分:8)
在我们进入过早优化模式之前,查看以下查询模板可能很有用。如果没有别的可以用作可以衡量可能优化效果的基线。
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
子查询是“驱动查询”,即对应于最终用户初始标准的查询。 (有关此查询如何多次可能适合整体优化流程的详细信息,请参阅下文) 注释是T1上的JOIN(可能是T2,T3,当选择了几个标签时),以及与WHERE子句相关的标准。当用户选择特定标签时,无论是作为初始搜索的一部分还是通过细化,都需要这些。 (放置这些连接以及子查询中的where子句可能更有效;更多关于以下内容)
<强>讨论... 强> 出于两个不同的目的,需要“驱动查询”或其变体:
请注意,完整列表不需要排序(或者它可能会受益于以不同顺序排序),因此第二个列表需要根据用户的选择进行排序(例如按日期,降序或按标题排序)按字母顺序排列)。另请注意,如果需要任何排序顺序,查询的成本将意味着处理完整列表(不熟悉SQL本身的奇怪优化,和/或一些非规范化,SQL需要“看到”该列表上的最后记录,如果他们属于顶部,排序方式)。
后一个事实,赞成为两个目的使用相同的查询,相应的列表可以存储在临时表中。一般流程是快速查找前N个项目记录及其详细信息,并立即将其返回给应用程序。然后,应用程序可以获得ajax-fashion标签列表以进行优化。此列表将使用类似于上面的查询生成,其中子查询由“select * from temporaryTable”替换。 SQL优化器将决定对此列表进行排序(在某些情况下)的可能性很大,让我们让它做到这一点,而不是第二次猜测并明确地对其进行排序。
要考虑的另一点是,可以将ItemTagMap表中的连接带到“驱动查询”中,而不是如上所示。最好这样做,既可以提高性能,也可以为#2目的生成正确的列表(显示一个项目页面)。
上述查询/流程可能会很好地扩展,即使在相对适中的硬件上也是如此;暂时进入1/2万+项目,持续的用户搜索可能达到每秒10次。其中一个关键因素是初始搜索标准的选择性。
优化提示
- ''nough说! -
应根据实际要求和有效数据统计资料选择适当的架构和优化......
答案 1 :(得分:0)
假设:
然后:
SELECT t.name
FROM Tag t
WHERE EXISTS (SELECT 1 FROM ItemTag WHERE item_id = 1234)
ORDER BY t.name
没有什么密集的。这是类似的,但我猜它会慢一点:
SELECT t.name
FROM Tag t
WHERE t.id IN (SELECT tag_id FROM ItemTag WHERE item_id = 1234)
ORDER BY t.name
这也可以作为连接完成:
SELECT DISTINCT t.name
FROM Tag t
JOIN ItemTag i WHERE i.tag_id = t.id
WHERE i.item_id = 1234
ORDER BY t.name
我认为第一个会更快但是SQL的情况总是如此,值得测试(在足够大的数据集上)。
上面已经完成了列出单个项目的标签。您需要一组用于搜索结果的标签。从上述情况来看并不困难,但这取决于您获得搜索结果的方式。
答案 2 :(得分:0)
您需要尝试最小化数据库调用次数,将繁重的工作放入PHP中。
首先,从数据库中选择所有项目:
select * from items where (conditions);
然后,从结果集中创建一个包含所有id的数组。
$ids = array();
foreach ($items as $item) {
$ids[] = $item['id'];
}
$ids = implode(',' $ids);
然后为您之前检索的项目ID选择所有ItemTagMaps和相关标签数据。
select map.item_id, t.id, t.name from tags t, item_tag_maps map where t.id = map.tag_id and map.item_id in ($ids);
现在,当您遍历$ items数组时,只要具有匹配的item_id值,就可以从执行的第二个SQL查询中找到所有匹配的标记。