MySQL:使用三个联接优化查询

时间:2017-11-08 15:53:25

标签: mysql database-design

首先是第一件事:我正在做的事情非常好。我只是看看是否有任何改进的空间,如果我做的事情是标准的和/或使用良好的做法。

这些是有问题的表格:

  • item
  • topic
  • item_topic
  • item_like_audit

这是我的用例:

  • topic个,可以包含多个item
  • 每个item都可以包含N个喜欢的内容。
  • 对于每个类似,记录存储在item_like_audit表中,以便稍后可以查询以进行排名。

这是查询尝试实现的目的:

  • 获取过去7天内收到最多喜欢的特定主题下的所有项目。

可以以任何方式改进以下查询或底层架构(性能或内存增益)吗?

查询:

SELECT DISTINCT item.* FROM item

/* Match items under this specific topic */
JOIN topic
    ON topic.slug = ?
    AND topic.deleted_at IS NULL
JOIN item_topic
    ON item_topic.item_id = item.id
    AND item_topic.topic_id = topic.id
    AND item_topic.deleted_at IS NULL

/* Match items that have had "like" activity in the past 7 days */
JOIN item_like_audit
    ON item_like_audit.item_id = item.id
    AND item_like_audit.created_at <= (CURRENT_DATE + INTERVAL 7 DAY)
WHERE item.deleted_at IS NULL

/* Order by highest like count to lowest */
ORDER BY item.like_count DESC

/* Pagination */
LIMIT ? OFFSET ?

架构:

CREATE TABLE item (
    id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,

    name VARCHAR(255) NOT NULL,
    slug VARCHAR(255) NOT NULL UNIQUE,
    tagline VARCHAR(255) NOT NULL,
    description VARCHAR(1000) NOT NULL,
    price FLOAT NOT NULL,
    like_count INT(10) NOT NULL DEFAULT 0,
    images VARCHAR(1000) NOT NULL,

    created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP NULL DEFAULT NULL,

    PRIMARY KEY (id)
);

CREATE TABLE item_like_audit (
    id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,

    item_id INT(10) UNSIGNED NOT NULL,
    user_id INT(10) UNSIGNED NOT NULL,

    created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,

    PRIMARY KEY (id),
    KEY `item_like_audit_created_at_index` (`created_at`)
);

CREATE TABLE topic (
    id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,

    name VARCHAR(255) NOT NULL,
    slug VARCHAR(255) NOT NULL UNIQUE,

    created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP NULL DEFAULT NULL,

    PRIMARY KEY (id)
);

CREATE TABLE item_topic (
    id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,

    item_id INT(10) NOT NULL,
    topic_id INT(10) NOT NULL,

    created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP NULL DEFAULT NULL,

    PRIMARY KEY (id)
);

2 个答案:

答案 0 :(得分:1)

由于您只返回项目记录,因此您可以尝试这样做以提高性能:

select Item.* 
  from Item
 where Item.deleted_at is null
   and exists (select 1 from item_topic
                where item_topic.item_id = item.id
                  and itme_topic.deleted_at is null
                  and exists (select 1 from topic
                               where topic.id = item_topic.item_id
                                 and topic.deleted_at is null
                                 and topic.slug = ?))
   and exists (select 1 from item_like_audit
                where item_like_audit.item_id = item.id
                  and item_liek_audit.created_at >= (current_date - interval 7 day))
 order by Item.like_count desc

这可以提高性能,因为:

  • 您不需要DISTINCT运营商
  • 数据库只需要从每个支持表中找到与约束匹配的1行而不是所有匹配的记录。

答案 1 :(得分:1)

假设item_topic(item_id,topic_id)是唯一的,我们可以取消使用&#34;使用filesort&#34;通过删除DISTINCT关键字并将item_like_audit的检查重写为EXISTS相关子查询而不是JOIN操作来进行操作。

如果我们有

,我们可以保证唯一性
  CREATE UNIQUE INDEX item_topic_UX1 ON item_topic (topic_id, item_id);

我们已经保证topic(slug)topic(id)item(id),......的独特性

  SELECT item.* 
    FROM item

/* Match items under this specific topic */
    JOIN item_topic
      ON item_topic.item_id = item.id
     AND item_topic.deleted_at IS NULL
    JOIN topic
      ON topic.id    = item_topic.topic_id
     AND topic.slug  = ?
     AND topic.deleted_at IS NULL

   WHERE item.deleted_at IS NULL
/* Match items that have had "like" activity in the past 7 days */
     AND EXISTS ( SELECT 1
                    FROM item_like_audit
                   WHERE item_like_audit.item_id = item.id
                     AND item_like_audit.created_at >= DATE(NOW()) + INTERVAL -7 DAY
                 )

/* Order by highest like count to lowest */
  ORDER BY item.like_count DESC

为了提高相关子查询的性能,我们可以创建覆盖索引

  CREATE INDEX item_like_audit_IX1 ON item_like_audit (item_id, created_at)

我们希望我们之前创建的唯一索引将用于连接操作,因此这也应该提高性能。如果我们包含deleted_at

,我们可以获得覆盖索引
  CREATE INDEX item_topic_IX2 ON item_topic (topic_id, item_id, deleted_at)

对于我们之前创建的唯一索引而言,这是多余的,如果我们仍然想要保证唯一性,请翻转列的顺序...

  DROP INDEX item_topic_UX1 ON item_topic ;
  CREATE UNIQUE INDEX item_topic_UX1 ON item_topic (item_id,topic_id);

如果我们不保证唯一性,那么我建议在GROUP BY item.id关键字上添加DISTINCT条款。

使用EXPLAIN查看执行计划,并验证是否正在使用适当的索引。

如果我们无法保证来自(item_id,topic_id)的{​​{1}}的唯一性,以及&#34;使用filesort&#34; item_topic操作的操作仍然太高,

我们可以尝试检查&#34;匹配主题&#34;条件使用EXISTS。 (但我并不希望这会更快。)

GROUP BY

我们需要有适当的索引来执行相关子查询。