【问题标题】:How to Optimized performance of JOIN query on large table如何优化大表的 JOIN 查询性能
【发布时间】:2019-12-27 08:00:18
【问题描述】:

我正在使用服务器版本:5.5.28-log MySQL Community Server (GPL)。

我有一个包含 279703655 条记录的大表,称为表 A。我必须在此表上执行与我的更改日志表 B 之一的连接,然后在新的 tmp 表 C 中插入匹配的记录。

B 表在列类型上有索引。

一个表由 prod_id、his_id 和其他列组成。一个表在 prod_id、history_id 列上都有索引。

当我要执行以下查询时

INSERT INTO C(prod,his_id,comm) 
SELECT DISTINCT a.product_id,a.history_id,comm
        FROM B as b INNER JOIN A as a ON a.his_id = b.his_id AND b.type="applications"
        GROUP BY prod_id
ON DUPLICATE KEY UPDATE
    `his_id` = VALUES(`his_id`);

插入记录需要 7 到 8 分钟。

即使我从表 A 执行简单的计数,也需要 15 分钟才能给我计数。

我也尝试过在 Limit 中插入记录的过程,但由于计数查询需要 15 分钟,它比以前慢了。

BEGIN
DECLARE n INT DEFAULT 0;
DECLARE i INT DEFAULT 0;
SELECT COUNT(*) FROM A INTO n;
SET i=5000000;
WHILE i<n DO 
 INSERT INTO C(product_id,history_id,comments) 
        SELECT a.product_id,a.history_id,a.comments FROM B as b 
            INNER JOIN (SELECT * FROM A LIMIT i,1) as a ON a.history_id=b.history_id;
  SET i = i + 5000000;
END WHILE;
End

但上述代码也需要 15 到 20 分钟才能执行。

请建议我如何让它更快。

下面是EXPLAIN结果:

+----+-------------+-------+--------+---------------+---------+---------+-----------------+--------------+-------------+
| id | select_type | table |  type  | possible_keys |   key   | key_len |       ref       |     rows     |    Extra    |
+----+-------------+-------+--------+---------------+---------+---------+-----------------+--------------+-------------+
|  1 | SIMPLE      | a     | ALL    | (NULL)        | (NULL)  | (NULL)  | (NULL)          |    279703655 |             |
|  1 | SIMPLE      | b     | eq_ref | PRIMARY       | PRIMARY | 8       | DB.a.history_id |            1 | Using index |
+----+-------------+-------+--------+---------------+---------+---------+-----------------+--------------+-------------+

(来自评论)

CREATE TABLE B (
    history_id bigint(20) unsigned NOT NULL AUTO_INCREMENT, 
    history_hash char(32) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, 
    type enum('products','brands','partnames','mc_partnames','applications') NOT NULL, 
    stamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 
    PRIMARY KEY (history_id), 
    UNIQUE KEY history_hash (history_hash), 
    KEY type (type), 
    KEY stamp (stamp)
); 

【问题讨论】:

  • GROUP BY prod_id 在您选择的其他列中没有意义。您可能想要添加示例数据来演示您在此处尝试执行的操作。
  • 请在您的查询中也发布EXPLAIN 结果,谢谢
  • 我也在上面的查询中选择 product_id。这是上述查询的解释结果。 id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE a ALL 279703655 1 SIMPLE b eq_ref PRIMARY PRIMARY 8 DB.a.history_id 1 使用索引
  • @Tim Biegeleisen 实际上我有更改日志表,其中我最近 7 天更改了添加/编辑/更新记录,在上述情况表 B 中。表 A 包含所有应用程序历史记录。我只需要获取最近 7 天更改过的应用记录。
  • 最近 7 天更改了吗?然而,使用的 TIMESTAMP 或 DATETIME 没有标准?我希望至少一个变更日志表应该有这样的列。

标签: mysql sql optimization


【解决方案1】:

让我们先看一下表格。

  • 所谓的表 B 实际上是一个历史表。它的主键是history_id
  • 所谓的表 A 实际上是一个产品表,每行一个产品,product_id 是它的主键。每个产品还有一个history_id。因此,您创建了一个 1:n 关系。一个产品有一个历史行;一个历史记录行涉及多个产品。

您正在选择具有“应用程序”类型历史条目的产品表行。这应该写成:

select product_id, history_id, comm
from product
where history_id in 
(
  select history_id
  from history 
  where type = 'applications'
);

(连接也可以,但不太清楚。因为每个产品只有一个历史记录行,所以不能重复。GROUP BYDISTINCT 在您的查询中完全是多余的并且应该被删除,以免给 DBMS 不必要的工作。但如前所述:最好不要加入。如果您想要表 A 中的行,请从表 A 中选择。如果您想查找表中的行B,在所有条件所属的WHERE 子句中查找它们。)

现在,我们必须知道有多少行可能会受到影响。如果所有历史记录行中只有 1% 是“应用程序”,则应使用索引。最好

create index idx1 on history (type, history_id);

...它通过type 查找行并立即获取它们的history_id

如果所有历史记录行中有 20% 是“应用程序”,那么按顺序读取表可能会更有效。

那么,我们可以获得多少产品行?即使只有一个历史记录行,我们也可能获得数百万个相关的产品行。反之亦然,有数百万历史记录行,我们可能根本没有产品行。同样,我们可以提供一个索引,它可能会或可能不会被 DBMS 使用:

create index idx2 on product (history_id, product_id, comm);

这几乎是最快的。提供了两个索引和一个正确的书面查询,没有不必要的连接。有时 MySQL 会遇到IN 的性能问题。人们随后用EXISTS 重写了该子句。我认为这仍然没有必要。

从 MySQL 8.0.3 开始,您可以为表创建直方图统计信息。

analyze history update histogram on type;
analyze product update histogram on history_id;

这是帮助优化器找到选择数据的最佳方式的重要一步。

【讨论】:

    【解决方案2】:

    需要索引(假设是history_id,而不是his_id):

    B:  INDEX(type, history_id) -- in this order.  Note: "covering"
    A:  INDEX(history_id, product_id, comm)
    

    哪一列或哪一列组合提供了 IODKU 需要的唯一性约束?

    真的--提供SHOW CREATE TABLE

    【讨论】:

    • 创建表B (history_id bigint(20) unsigned NOT NULL AUTO_INCREMENT, history_hash char(32) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, type enum('products', 'brands','partnames','mc_partnames','applications') NOT NULL, stamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (history_id), UNIQUE KEY history_hash (history_hash), KEY @987654333 @ (type), 关键stamp (stamp));
    • his_idhistory_id 一样吗? SHOW CREATE TABLE C 怎么样
    • 为我的错误道歉。是的 his_id = history_id 和 prod_id = product_id。表 C 是从上面创建的 select....查询。
    • @RupeshRanjangaonkar - 更新
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-06
    • 2011-02-14
    • 1970-01-01
    相关资源
    最近更新 更多