【问题标题】:How to optimize MySQL query with JOIN and WHERE?如何使用 JOIN 和 WHERE 优化 MySQL 查询?
【发布时间】:2022-01-30 12:27:30
【问题描述】:

我有两个表:shows(横幅展示),clicks(横幅点击)。

CREATE TABLE `shows` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`data` text NOT NULL,
`created_at` date NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `clicks` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`show_id` int(11) unsigned NOT NULL,
`data` text NOT NULL,
`created_at` date NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `created_at` (`created_at`),
KEY `show_constaraint_idx` (`show_id`),
CONSTRAINT `show_constaraint` FOREIGN KEY (`show_id`) REFERENCES `shows` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

每个表有 40 000 000 条记录。

我在显示表中添加了 40 000 000 条记录:

drop procedure if exists doWhile;
DELIMITER //
CREATE PROCEDURE doWhile()
BEGIN
DECLARE i INT DEFAULT 1;
WHILE (i <= 40000000) DO
INSERT INTO `shows` (data, created_at) values (
    CONCAT(MD5(RAND()), MD5(RAND())),
    FROM_UNIXTIME(unix_timestamp('2021-01-01') + floor(rand() * (unix_timestamp('2021-12-31') - unix_timestamp('2021-01-01') + 1)))
);
SET i = i + 1;
END WHILE;
END;
//
CALL doWhile();

我在 clicks 表中添加了 40 000 000 条记录:

drop procedure if exists doWhile;
DELIMITER //
CREATE PROCEDURE doWhile()
BEGIN
DECLARE i INT DEFAULT 1;
WHILE (i <= 40000000) DO
INSERT INTO `clicks` (show_id, data, created_at) values (
    (FLOOR(1 + RAND() * 40000000)),
    CONCAT(MD5(RAND()), MD5(RAND())),
    FROM_UNIXTIME(unix_timestamp('2021-01-01') + floor(rand() * (unix_timestamp('2021-12-31') - unix_timestamp('2021-01-01') + 1)))
);
SET i = i + 1;
END WHILE;
END;
//
CALL doWhile();

SQL 查询执行时间为 10 秒:

SELECT shows.id,
    shows.data,
    clicks.id,
    clicks.data
FROM clicks
INNER JOIN shows ON shows.id = clicks.show_id
WHERE shows.created_at = '2021-03-03'
AND clicks.created_at >= '2021-03-03'
AND clicks.created_at <= '2021-03-06'

我尝试优化 SQL 查询,但是 SQL 查询执行时间是 10 秒:

SELECT sub_shows.id,
    sub_shows.data,
    sub_clicks.id,
    sub_clicks.data
FROM (
    SELECT clicks.id,
        clicks.show_id,
        clicks.data
    FROM clicks
    WHERE clicks.created_at >= '2021-03-03' AND clicks.created_at <= '2021-03-06'
) as sub_clicks
INNER JOIN (
    SELECT shows.id,
        shows.data
    FROM shows
    WHERE shows.created_at = '2021-03-03'
) as sub_shows ON sub_shows.id = sub_clicks.show_id

两个查询的解释相同:

如何优化查询?

回答: 将 innodb_buffer_pool_size 更改为 12G (里克·詹姆斯)

【问题讨论】:

  • 在查询前运行 EXPLAIN 并将结果发布在问题上
  • 如果有任何条目,请查看 mysql 错误日志。您也可以为两个表尝试组合键而不是单独的键
  • 这是什么版本的mysql,因为在8.0.27这个:created_at date NOT NULL DEFAULT current_timestamp() throws:ERROR 1067 (42000): Invalid default value for 'created_at'
  • 多了一层子查询;摆脱它;然后我们可以进一步讨论。
  • innodb_buffer_pool_size改成12G,然后再检查时序。

标签: mysql join where-clause


【解决方案1】:

最好有一个复合索引涵盖连接条件的两个部分与个人。

Suggest Indexes as listed
Shows    ( created_at, id )
Clicks   ( show_id, created_at )

这两个键都有帮助的原因是索引利用了这两个部分,而无需转到原始数据页面来限定记录。只有在限定之后才需要检索结果集中的附加列。

我还会将查询调整并反转为最小粒度项目...在查询的主要 FROM 位置中的单个日期上的 SHOWS 表。

SELECT 
      shows.id,
      shows.data,
      c.id,
      c.data
   FROM
      shows s
         JOIN clicks c
            ON s.id = c.show_id
          AND c.created_at >= '2021-03-03'
          AND c.created_at <= '2021-03-06'   
   WHERE 
      s.created_at = '2021-03-03'

【讨论】:

  • 我将索引替换为复合索引。显示:KEY created_at_id (created_at,id) 点击:KEY show_id_created_at (show_id,created_at) 我的查询执行时间是 12 秒。我优化的查询执行时间是 12 秒。您的查询执行时间是 12 秒。
猜你喜欢
  • 2012-01-28
  • 1970-01-01
  • 2021-08-25
  • 2011-02-14
  • 1970-01-01
  • 1970-01-01
  • 2015-08-28
  • 1970-01-01
相关资源
最近更新 更多