【问题标题】:Django slow inner join on a table with more than 10 million recordsDjango 在具有超过 1000 万条记录的表上进行慢速内连接
【发布时间】:2021-10-11 06:56:36
【问题描述】:

我在 Django 中使用 mysql。我正在尝试计算特定经销商在一定时间内的visitor_pages 数量。 我将分享我从 django 调试工具栏获得的原始 sql 查询。

SELECT COUNT(*) AS `__count`
  FROM `visitor_page`
INNER JOIN `dealer_visitors`
    ON (`visitor_page`.`dealer_visitor_id` = `dealer_visitors`.`id`)
WHERE (`visitor_page`.`date_time` BETWEEN '2021-02-01 05:51:00'
                                      AND '2021-03-21 05:50:00'
  AND `dealer_visitors`.`dealer_id` = 15)

问题是我在visitor_pages 表中有超过1300 万条记录,在dealer_visitor 表中有大约150 万条记录。我已经索引了 date_time。我正在考虑使用物化视图,但在尝试之前,我非常感谢有关如何改进此查询的建议。

visitor_pages 架构:

CREATE TABLE `visitor_page` (
  `id` int NOT NULL AUTO_INCREMENT,
  `date_time` datetime(6) DEFAULT NULL,
  `added_at` datetime(6) DEFAULT NULL,
  `updated_at` datetime(6) DEFAULT NULL,
  `page_id` int NOT NULL,
  `dealer_visitor_id` int NOT NULL,
  PRIMARY KEY (`id`),
  KEY `visitor_page_page_id_246babdf_fk_web_page_id` (`page_id`),
  KEY `visitor_page_dealer_visitor_id_e2dddea2_fk_dealer_visitors_id` (`dealer_visitor_id`),
  KEY `visitor_page_date_time_06e9e9f5` (`date_time`),
  CONSTRAINT `visitor_page_dealer_visitor_id_e2dddea2_fk_dealer_visitors_id` FOREIGN KEY (`dealer_visitor_id`) REFERENCES `dealer_visitors` (`id`),
  CONSTRAINT `visitor_page_page_id_246babdf_fk_web_page_id` FOREIGN KEY (`page_id`) REFERENCES `web_page` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13626649 DEFAULT CHARSET=latin1;

dealer_visitors 架构:

CREATE TABLE `dealer_visitors` (
  `id` int NOT NULL AUTO_INCREMENT,
  `visit_date` datetime(6) DEFAULT NULL,
  `added_at` datetime(6) DEFAULT NULL,
  `updated_at` datetime(6) DEFAULT NULL,
  `dealer_id` int NOT NULL,
  `visitor_id` int NOT NULL,
  `type` int DEFAULT NULL,
  `notes` longtext,
  `location` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `dealer_visitors_dealer_id_306e2202_fk_dealer_id` (`dealer_id`),
  KEY `dealer_visitors_visitor_id_27ae498e_fk_visitor_id` (`visitor_id`),
  KEY `dealer_visitors_type_af0f7d79` (`type`),
  KEY `dealer_visitors_visit_date_f2b138c9` (`visit_date`),
  CONSTRAINT `dealer_visitors_dealer_id_306e2202_fk_dealer_id` FOREIGN KEY (`dealer_id`) REFERENCES `dealer` (`id`),
  CONSTRAINT `dealer_visitors_visitor_id_27ae498e_fk_visitor_id` FOREIGN KEY (`visitor_id`) REFERENCES `visitor` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1524478 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

EXPLAIN ANALYZE 查询给了我以下信息:

解释:

【问题讨论】:

  • 你能分享你拥有的模型和任何索引吗?
  • read this 然后edit 你的问题。你没有给我们足够的信息来帮助你。如果您的查询很慢,那么您的索引就不太可能“顺利进行”。
  • 您已将datedate_time 编入索引?我问是因为您没有在查询中使用date。此外,为获得最佳支持,请包括实际的表定义,包括索引等,以及查询的实际EXPLAIN 计划; dev.mysql.com/doc/refman/8.0/en/explain.html#explain-analyze
  • @MatBailie 我已经索引了 date_time。编辑问题以添加更多详细信息

标签: mysql sql django query-optimization


【解决方案1】:

对于这个查询:

SELECT COUNT(*) AS `__count`
FROM visitor_page vp JOIN
     dealer_visitors dv
     ON vp.dealer_visitor_id = dv.id
WHERE vp.date_time BETWEEN '2021-02-01 05:51:00' AND '2021-03-21 05:50:00' AND
     dv.dealer_id = 15;

最佳索引位于dealer_visitors(dealer_id, date_time, id)visitor_page(dealer_visitor_id)

仅在 date 上的索引会有所帮助。但是您正在检索一个月的数据,这可能需要处理大量数据。将dealer_id 作为索引中的第一列会将数据限制为该交易商在该时间范围内的行。

【讨论】:

  • 感谢您的回复。这些索引已经创建,我已经分享了架构设计,这表明
  • @JawadChughtai - 三列上的一个索引与一列上的三个索引不同。
【解决方案2】:

根据数据的分布,优化器可能会选择其中一个表开始,或者选择另一个。因此,让我们为每种情况提供最佳索引:

   ON `visitor_page`.`dealer_visitor_id` = `dealer_visitors`.`id`
WHERE `visitor_page`.`date_time` BETWEEN ...
  AND `dealer_visitors`.`dealer_id` = 15

visitor_page开头:

 visitor_page:  INDEX(date_time)   -- (already exists)
 dealer_visitors:  (already has PRIMARY KEY(id))

dealer_visitors开头:

 dealer_visitors:  INDEX(dealer_id)   -- (already exists)
 visitor_page:  INDEX(dealer_visitor_id, date_time)  -- in this order

并删除dealer_visitors_visitor_id_27ae498e_fk_visitor_id,因为现在是多余的。

净是加一索引,减一索引。

物化视图 - 数据仓库报告通常最好构建并逐步维护“汇总表”(“物化视图”)。非常奇怪的日期范围(1 个月 + 20 天 - 61 秒)使这个操作很笨拙。通常,根据全天制作表格很方便。如果您可以切换到每天(或每小时),请参阅http://mysql.rjweb.org/doc.php/summarytables

要检查的其他内容:您有多少 RAM? SHOW VARIABLES LIKE 'innodb_buffer_pool_size'; 说什么?

我看到这些表有不同的字符集/排序规则。对于相关查询来说这不是问题,但如果您在 VARCHARs 上还有其他查询 JOIN,请检查它们是否使用相同的排序规则。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-03-06
    • 2014-07-09
    • 1970-01-01
    • 2020-09-28
    • 1970-01-01
    • 2021-05-29
    • 2012-01-09
    • 1970-01-01
    相关资源
    最近更新 更多