【发布时间】:2023-04-06 01:26:01
【问题描述】:
我定义了以下表格:
CREATE TABLE products (
product_id INTEGER(11) NOT NULL AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
section VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (product_id)
) ENGINE=MyISAM;
CREATE TABLE categories (
category_id INTEGER(11) NOT NULL AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
PRIMARY KEY (category_id)
) ENGINE=MyISAM;
CREATE TABLE product_categories (
product_id INTEGER(11) NOT NULL,
category_id INTEGER(11) NOT NULL,
PRIMARY KEY (product_id, category_id)
) ENGINE=MyISAM;
实际上还有更多,这是优化更大、更复杂的查询的一部分。其中一部分是将一些缓慢的子查询移动到视图中,到目前为止这已经很有帮助。
当我添加 categories/product_categories 表并在允许用户按 products.section 或 categories.category_id 搜索时加入它们时,查询变得非常慢。 UI 将这些作为搜索参数传入,我试图为每个产品获取一行,其中包含其 id、名称、部分以及与之关联的类别名称的逗号分隔列表。通过以下视图和查询,我能够加快速度:
CREATE OR REPLACE
ALGORITHM = MERGE
VIEW view_products_with_categories
AS
SELECT
p.product_id,
p.name,
p.section,
pc.name AS category
products p
LEFT JOIN product_categories pc on p.product_id = pc.product_id
LEFT JOIN categories c ON pc.category_id = c.category_id;
SELECT
product_id,
name,
section,
GROUP_CONCAT(DISTINCT category ORDER BY category) AS categories
FROM view_products_with_categories
GROUP BY product_id;
假设我们有以下行:
product_id name section category_id category
332913 Model Train Engine child-and-baby 1160 child-and-baby>baby-and-pre-schooltoys>playsets
332913 Model Train Engine child-and-baby 1308 toys>baby-and-preschool>playsets
332913 Model Train Engine child-and-baby 1312 toys>carstrains-and-planes>cars-and-vehicles
上面的简单查询给了我以下信息:
product_id name section categories
332913 Model Train Engine child-and-baby child-and-baby>baby-and-pre-schooltoys>playsets,toys>baby-and-preschool>playsets,toys>carstrains-and-planes>cars-and-vehicles
这很好,正如预期的那样。但是,我希望用户能够按 category_id 进行搜索。目前,我们的 UI 对类别名称进行了一些自动完成魔术,并为动态生成的 SQL 添加了一个过滤器,其中包含 category_id。如果我在 GROUP_CONCAT 查询中留下 category_id,它将是 1160。假设他们要搜索第二个 (1308),因此我们将查询修改如下:
SELECT
product_id,
name,
section,
GROUP_CONCAT(DISTINCT category ORDER BY category) AS categories
FROM view_products_with_categories
WHERE category_id = 1308
GROUP BY product_id;
现在我们得到以下信息:
product_id name section categories
332913 Model Train Engine child-and-baby toys>baby-and-preschool>playsets
再次,正是您所期望的。但是,客户希望查看与具有他们正在搜索的一个或多个类别的产品相关联的所有类别。因此,让我们制作一些简化的示例数据来向您展示我在寻找什么:
product_id name section category_id category
1 product_1 section_1 1 category_1
1 product_1 section_1 2 category_2
1 product_1 section_1 3 category_3
2 product_2 section_2 3 category_3
2 product_2 section_2 4 category_4
2 product_2 section_2 5 category_5
如果用户搜索 category_id = 3,我希望他们得到以下信息:
product_id name section categories
1 product_1 section_1 category_1, category_2, category_3
2 product_2 section_2 category_3, category_4, category_5
但我目前只得到:
product_id name section categories
1 product_1 section_1 category_3
2 product_2 section_2 category_3
我只是想不出没有子查询的方法,它的缓慢是我首先转向视图的原因。我希望我缺少一些非常明显的东西,可能是由于睡眠不足,所以任何帮助都将不胜感激。
更新:我还应该提到,产品可能不属于任何类别,因此我的代码中存在 LEFT JOIN。
【问题讨论】:
-
解决性能问题的正确方法是分析执行计划,如果你在查询前加上
EXPLAIN关键字就可以得到。您的子查询可能很慢,仅仅是因为product_category表中的category_id和products表中的section缺少索引。另外,您使用 MyISAM 引擎而不是可靠的事务性 InnoDB 是否有任何特殊原因? -
获取关于左连接的要点,但是,在您在此处陈述的问题中,您说“客户希望查看与具有他们正在搜索的一个或多个类别的产品相关联的所有类别为了”。这意味着您可以使用内部联接,不是吗? (因此在下面的解决方案中使用了内连接。
-
@piotrm:确实可能是由于这两个字段上缺少索引。使用 MyISAM 是因为最初设计该系统的顾问坚持要设置它。除了每晚的 ETL/聚合作业外,它是只读的,因此表级锁并不是真正的问题,并且 FK 完整性由 ETL 过程处理。他在其他一些事情上错了,所以我切换到 InnoDB 或者最好是 PostgreSQL 没有问题。
-
@danb:是的,如果他们正在搜索特定类别。他们可能只是在搜索特定的产品、部分或类别。或者全部 3 个,或者没有。我正在玩一个基于你的查询,它将我的视图与 product_id 上的 product_categories 结合起来,基本上有一个行的笛卡尔积,然后可以按 product_id、category_id 分组。希望今天下午有另一个更新。
-
@ChrisDoggett 在下面查看我的替代方案。它适用于您描述的情况并仍然使用该视图。
标签: mysql join sql-view sqlperformance