【问题标题】:Tagging query with group_concat使用 group_concat 标记查询
【发布时间】:2013-05-13 07:07:36
【问题描述】:

使用数据库模式来标记此问题已接受的answer 是否可以使用 group_concat 进行查询以处理大量数据?我需要为所有带有标签 x 的项目获取带有标签的项目。使用具有约 50 万个标签的 group_concat 查询在 > 15 秒时非常慢。没有 group_concat (items without 标签) 它是 ~ 0.05 秒。

作为一个附带问题,SO 如何解决这个问题?

【问题讨论】:

  • 能否提供样例记录。
  • SO 似乎通过将问题限制为最多 5 个标签来解决此问题。是什么让你认为它在处理标签时完全使用GROUP_CONCAT()
  • @Barmar:对SO的标签限制不是出于性能原因,而是to keep questions focused;至于SO's schema,标签以规范化方式(PostTags 表)和非规范化方式(Posts.Tags 字段)与帖子相关联 - 后者可以非常快速地检索带有帖子的帖子标签本身,而前者使搜索具有特定标签组合的帖子变得容易。

标签: mysql query-optimization


【解决方案1】:

这可能是索引策略不佳的情况。调整您链接到的问题的the accepted answer 中显示的架构:

CREATE Table Items (
  Item_ID    SERIAL,
  Item_Title VARCHAR(255),
  Content    TEXT
) ENGINE=InnoDB;

CREATE TABLE Tags (
  Tag_ID     SERIAL,
  Tag_Title  VARCHAR(255)
) ENGINE=InnoDB;

CREATE TABLE Items_Tags (
  Item_ID    BIGINT UNSIGNED REFERENCES Items (Item_ID),
  Tag_ID     BIGINT UNSIGNED REFERENCES Tags  ( Tag_ID),
  PRIMARY KEY (Item_ID, Tag_ID)
) ENGINE=InnoDB;

注意:

  • MySQL 的SERIAL 数据类型是BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE 的别名,因此被索引;

  • Items_Tags 中定义外键约束会在外键列上创建索引。

【讨论】:

  • 嗯,我很确定我确实有相同的索引,今晚晚些时候会检查。
【解决方案2】:

我建议在 normalized 数据和 denormalized 数据之间进行混合。
因此,使用 eggyal 提供的规范化结构,我将执行以下非规范化结构:

CREATE TABLE Items_Tags_Denormalized (
  Item_ID    BIGINT UNSIGNED REFERENCES Items (Item_ID),
  Tags     BLOB,
  PRIMARY KEY (Item_ID)
) ENGINE=InnoDB;

Tags 列中,您将拥有对应Item_ID 的所有标签(Tag_Title)。
现在你有两种方法来实现这一点:

  • 创建一个定期运行的 cron,它将使用 GROUP_CONCAT 或任何适合您的方式构建此表 Items_Tags_Denormalized(优点:在 Items_Tags 表中插入或删除时不会增加额外负载;缺点:非规范化表并不总是最新的(取决于您运行 cron 的频率))

  • 在插入和删除时为Items_Tags 表创建triggers,以使Items_Tags_Denormalized 表保持最新(优点:非规范化表将始终是最新的;缺点:当您在Items_Tags表中插入或删除)

考虑优缺点,选择最适合您需求的解决方案。

因此,最终您将获得Items_Tags_Denormalized 表,您将只读取而不执行其他操作

【讨论】:

  • 为什么不将非规范化的Tags 字段添加到Items 表中(SO 就是这样做的)?
  • 拥有单独的模型至关重要:一个标准化和一个非标准化,您的解决方案还可以,但从设计观点来看,我建议将其分开,原因有很多:您需要重建表,您需要添加更多列等加上如果您在项目中添加非规范化标签列,则表的性能将下降:更大的大小=更慢的查询
【解决方案3】:

你为什么要使用 group_concat 呢?对于给定的标签 x,您说选择项目列表很快。对于给定的项目列表,获取所有标签也应该很快。并且通常没有某种限制,我的意思是普通网站不会在一页上显示 100000 个条目。

我建议:

drop temporary table if exists lookup_item;

create temporary table lookup_item (item_id serial, primary key(item_id));

insert into lookup_item select i.id as item_id 
from items i 
where exists (select * from items_tags where item_id = i.id and tag_id = <tag_id>)
and <other conditions or limits>;

select * from lookup_item
inner join items_tags it on it.item_id = i.id
inner join tags t on t.id = it.tag_id
order by i.<priority>, t.<priority>

优先级可以是最后修改的项目和某种重要性的标签。

然后你会得到每个带有标签的项目。代码中唯一的工作是查看结果行何时包含下一项。

【讨论】:

    【解决方案4】:

    如果我理解正确的话,GROUP_CONCAT 并不是您要删除的唯一一个使查询在没有标签的情况下更快的东西。在GROUP_CONCAT 中,您选择Tags.Tag_Title 并强制访问标签表。

    您可以尝试运行 GROUP_CONCATItems_Tags.Tag_ID 来测试我的理论。

    【讨论】:

      猜你喜欢
      • 2020-09-23
      • 2014-03-09
      • 2012-10-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-18
      • 2018-05-12
      • 1970-01-01
      相关资源
      最近更新 更多