如何通过标签搜索数十亿个项目(寻找最佳架构)?

时间:2016-09-14 15:40:01

标签: mysql database performance optimization architecture

我有一个庞大的项目数据库,数十亿条目:

  

t_item

     

itemId ...

每个都标有多个标记

  

t_tag

     

tagId tagName

     

t_item_tag

     

itemTagId [AI] itemId tagId

最终用户的用例场景很简单:

  

选择标有特定标签的所有项目,并按某些属性对其进行排序。 E.g:

     
      
  • 使用 Core i7处理器(另一个标签)选择所有笔记本电脑(标签),并按评级(t_item表)排序;
  •   
  • 选择皮革(标签)制作的所有黄色(标签) 包(标签)并按价格(t_item)对其进行排序表)
  •   

目前我在MySQL上运行该系统,但它开始达到它的极限。主要关注的是最终排序已完成使用临时;使用filesort ,这有点慢。

我可以使用哪些其他架构来处理我的用例和卷?

1 个答案:

答案 0 :(得分:1)

“使用temp;使用filesort”不是恶棍,架构和索引都是。

这是tags的最佳选择:

CREATE TABLE Tags (
    item_id ...,
    tag ... -- either a string or a tag_id, it does not matter much,
    PRIMARY KEY(tag, item_id),  -- for clustering and searching
    INDEX(item_id)  -- for maintenance (eg deleting an item_id)
    -- no FOREIGN KEYs; they slow things down
    -- no AUTO_INCREMENT; it is a total waste here
) ENGINE=InnoDB; -- so PK will be clustered.

因此,即使有数十亿行,这样的表也非常有效 - 所有“黄色”项目都将“聚集”并且只需要很少的磁盘命中。

(您建议使用tag_id和另一张表。我认为关于这是否更好是一个折腾。)

计算磁盘命中数 - 它们是巨型表的主要性能指标。

select all laptops (tag) with Core i7 processor (another tag) and sort them by rating (t_item table); -

SELECT i.id
    FROM Items AS i
    JOIN Tags  AS t1  ON t1.item_id = i.id
    JOIN Tags  AS t2  ON t2.item_id = i.id
    WHERE t1.tag = 'laptops'
      AND t2.tag = 'Core i7 processor'
    ORDER BY i.rating DESC
    LIMIT 10;

如果有2000台'笔记本电脑',则可能是20个磁盘块(计为20个磁盘命中)。 150'核心i7处理器'可能还有2个磁盘命中率。如果那些导致70'项目',那么你将获得70多个磁盘命中,因为它们可能是随机定位的。是的,会有一个临时表(在RAM中)和一个filesort(但没有物理“文件”)来排序70个ID并提供10个。

是的,您必须动态构建此SELECT

我因为几个原因而停止了(仅获取i.id)......

Items还有什么?一些大的TEXT列(description等)?他们是令人讨厌的;他们把努力弄得一团糟。

希望是SELECT i.*代替SELECT i.id。让我们看看我们是否可以妥协。

假设您有INDEX(id, rating)。但是有充分的理由使用 idrating制作表格 - 您经常更改我们需要“小”表用于通过评级查找和排序70个ID。

所以,我们有两个表:Items - id,描述等,而Ratings只有:

CREATE TABLE Ratings (
    item_id -- 1:1 with `Items.id`
    rating ...,
    PRIMARY KEY(id)
) ENGINE=InnoDB;

现在,将我的第一个查询更改为FROM Ratings(而不是FROM Items)。现在,我们仍然需要从该表中获取70行,但由于它较小,因此缓存的可能性更大,因此可能涉及少于70次磁盘命中。

然后,我们需要的最终提取来自Items的10件大件事。

总计:20 + 2 + 70 + 10 = 102次磁盘命中,或普通旧旋转驱动器上约1秒; SSD上的速度要快得多。 (另外希望最大的数字,70,会更低。)

回到filesort ...通过只有'小'列(id,rating)并且只有70'行'进行排序,filesort将是一个内存结构,几乎没有时间执行。这就是为什么我说要忽略filesort。通过安排稍后获取庞大的description ,我避免通过filsort拖运它,这几乎肯定会迫使tmp表成为MyISAM,而不是MEMORY。

既然您提到按评级或价格排序,也许这很好:

CREATE TABLE RatingsPrices (
    item_id ..., -- 1:1 with `Items.id`
    rating ...,
    price ...,
    INDEX(item_id, rating),  -- covering index for the main query
    INDEX(item_id, price),  -- covering index for the main query
) ENGINE=InnoDB;

可能有更多细节和细微之处,但我希望这些提示可以让你朝着一个好方向前进。

请记住:如果数据集太大,即使工作集也不适合RAM,“计算磁盘命中数”。