在具有数百万条记录的 2 个表上加入更快

Posted

技术标签:

【中文标题】在具有数百万条记录的 2 个表上加入更快【英文标题】:Making join on 2 tables with millions record to be faster 【发布时间】:2014-07-21 11:46:33 【问题描述】:

我有两张桌子

security_stat => 拥有 400 万条记录

security_trade => 拥有 1000 万条记录

我已经成功运行了这个查询,但是我如何优化这能够在 10 秒内至少查询 100,000 条记录(有可能吗?).. 目前它非常非常慢。

SELECT `sec_stat_sec_name`, `sec_stat_date`, `sec_stat_market`, `sec_trade_close`, `sec_stat_date` 
FROM security_stat` LEFT JOIN `security_trade` 
ON `security_trade`.`sec_trade_sec_name` = `security_stat`.`sec_stat_sec_name` 
    and `security_trade`.`sec_trade_date` = `security_stat`.`sec_stat_date` 
limit 100,000

我有关于 sec_trade_sec_name、sec_stat_sec_name、sec_trade_date 和 sec_stat_date 的索引

我尝试使用 WHERE sec_stat_date >= 2005-01-01 来限制结果,但这并没有多大帮助。 (我的记录范围从 1975 年到 2014 年)

编辑

security_stat 架构

CREATE TABLE `security_stat` (
  `sec_stat_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `sec_stat_date` date NOT NULL,
  `sec_stat_sec_name` varchar(255) NOT NULL,
  `sec_stat_sec_id` int(11) NOT NULL,
  `sec_stat_market` varchar(255) NOT NULL,
  `sec_stat_industry` int(11) NOT NULL,
  `sec_stat_sector` int(11) NOT NULL,
  `sec_stat_subsector` int(11) NOT NULL,
  `sec_stat_sec_type` varchar(1) NOT NULL,
  `sec_stat_status` varchar(2) NOT NULL,
  `sec_stat_benefit` varchar(2) NOT NULL,
  `sec_stat_listed_share` bigint(20) NOT NULL,
  `sec_stat_earn_p_share` decimal(12,5) NOT NULL,
  `sec_stat_value` decimal(9,2) NOT NULL,
  `sec_stat_p_of_earn` int(11) NOT NULL,
  `sec_stat_as_date` date NOT NULL,
  `sec_stat_div_p_share` decimal(16,12) NOT NULL,
  `sec_stat_p_of_div` int(11) NOT NULL,
  `sec_stat_end_date_div` date NOT NULL,
  `sec_stat_pe` decimal(8,2) NOT NULL,
  `sec_stat_pbv` decimal(8,2) NOT NULL,
  `sec_stat_div_yield` decimal(8,2) NOT NULL,
  `sec_stat_par_value` decimal(16,5) NOT NULL,
  `sec_stat_market_cap` decimal(20,2) NOT NULL,
  `sec_stat_turn_ratio` decimal(8,2) NOT NULL,
  `sec_stat_npg_flag` varchar(1) NOT NULL,
  `sec_stat_acc_div` decimal(16,12) NOT NULL,
  `sec_stat_acc_no_of_pay` int(11) NOT NULL,
  `sec_stat_div_pay_ratio` decimal(6,2) NOT NULL,
  `sec_stat_earn_date` date NOT NULL,
  `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `sec_stat_ev` decimal(20,2) DEFAULT NULL,
  `sec_stat_ev_revenue` decimal(20,2) DEFAULT NULL,
  `sec_stat_ev_ebit` decimal(20,2) DEFAULT NULL,
  `sec_stat_ev_ebitda` decimal(20,2) DEFAULT NULL,
  `sec_stat_earning_yield` decimal(10,5) DEFAULT NULL,
  `sec_stat_ps_ratio` decimal(10,5) DEFAULT NULL,
  PRIMARY KEY (`sec_stat_id`),
  UNIQUE KEY `sec_stat_date_name_id_cap` (`sec_stat_date`,`sec_stat_market`,`sec_stat_sec_id`,`sec_stat_sector`),
  KEY `sec_stat_date` (`sec_stat_date`),
  KEY `sec_stat_sec_name` (`sec_stat_sec_name`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=3598612 ;

security_trade 架构

CREATE TABLE `security_trade` (
  `sec_trade_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `sec_trade_date` date NOT NULL,
  `sec_trade_sec_name` varchar(20) NOT NULL,
  `sec_trade_sec_id` int(11) NOT NULL,
  `sec_trade_market` varchar(1) NOT NULL,
  `sec_trade_trading_method` varchar(1) NOT NULL,
  `sec_trade_trade_report` varchar(1) NOT NULL,
  `sec_trade_prior_date` date NOT NULL,
  `sec_trade_prior` decimal(8,2) NOT NULL,
  `sec_trade_open` decimal(8,2) NOT NULL,
  `sec_trade_high` decimal(8,2) NOT NULL,
  `sec_trade_low` decimal(8,2) NOT NULL,
  `sec_trade_close` decimal(8,2) NOT NULL,
  `sec_trade_last_bid` decimal(8,2) NOT NULL,
  `sec_trade_last_offer` decimal(8,2) NOT NULL,
  `sec_trade_transaction` int(11) NOT NULL,
  `sec_trade_volume` bigint(20) NOT NULL,
  `sec_trade_value` decimal(20,2) NOT NULL,
  `sec_trade_avg_price` decimal(8,2) NOT NULL,
  `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (`sec_trade_id`),
  UNIQUE KEY `sec_trade_close` (`sec_trade_date`,`sec_trade_sec_name`,`sec_trade_market`,`sec_trade_trade_report`,`sec_trade_trading_method`),
  KEY `security_trade_sec_trade_sec_name_index` (`sec_trade_sec_name`),
  KEY `security_trade_sec_trade_date_index` (`sec_trade_date`),
  KEY `security_trade_sec_trade_prior_date_index` (`sec_trade_prior_date`),
  KEY `security_trade_sec_trade_close_index` (`sec_trade_close`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=10019817 ;

我的最终查询实际上会有更多

WHERE sec_stat_earning_yield IS NULL
ORDER BY updated_at ASC

但是因为当我将这两条语句添加到 LIMIT 1,000 条记录的查询中时,它会使我的查询更慢(可能是因为我在这两列上没有索引?)

提前致谢

【问题讨论】:

运行explain select ... 亲自查看 您在表上有哪些索引?这些索引是否对应于连接和/或您的 where 子句?您指定了 100k 记录的限制,但没有 order by 子句 (您想要随机的 100k 记录、最近的 100k 记录等吗?) 您没有指定架构,所以我们无法判断sec_trade_sec_name, sec_trade_date 在一个和/或另一个表中是否唯一。 感谢您的评论,我用架构更新了我的问题。我实际上想再有一个 WHERE 子句并按 updated_at 排序,但这使我的查询甚至更慢,(可能是因为我在那些列上没有 index ?) 请注意,没有 ORDER BY 的 LIMIT 几乎没有意义 (我很高兴我不必使用这个命名约定) 【参考方案1】:

将以下内容作为您的实际查询:

SELECT `sec_stat_sec_name`, `sec_stat_date`, `sec_stat_market`, `sec_trade_close`, `sec_stat_date` 
FROM `security_stat` LEFT JOIN `security_trade` 
ON `security_trade`.`sec_trade_sec_name` = `security_stat`.`sec_stat_sec_name` 
    and `security_trade`.`sec_trade_date` = `security_stat`.`sec_stat_date` 
WHERE sec_stat_earning_yield IS NULL
ORDER BY updated_at ASC
limit 100,000

您可以通过两种方式过滤security_stat 表: 1.只有sec_stat_earning_yield IS NULL 2.updated_at排序的前100k条记录

注意:我假设你的意思是security_stat.updated_at,但你没有说清楚。

为了使其尽可能便宜,请添加一个涵盖这两个字段 (sec_stat_earning_yield, updated_at) 的索引。

注意:添加变化很大的索引,尤其是当索引中记录的顺序发生变化时,可能会使 INSERT 变慢。 需要平衡 INSERT 性能和 SELECT 性能。

然后您加入交易表,因此您希望查找尽可能快,这可以通过该表上覆盖(sec_trade_sec_name, sec_trade_date, sec_trade_close) 的索引来实现。 - 索引中的前两个字段使查找更简单 - 索引中的最后一个字段意味着 DBMS 可以避免在表中查找

完成后,您还可以查看 EXPLAIN 计划,尽管它相对复杂,但它会为您提供关键信息,以了解优化目标的最佳位置。

【讨论】:

【参考方案2】:

首先,尝试创建索引以匹配连接:

security_trade (sec_trade_sec_name, sec_trade_date)

security_stat (sec_stat_sec_name, sec_stat_date)

或者可能

security_stat(sec_stat_earning_yield、sec_stat_sec_name、sec_stat_date)

正如上面 cmets 中所指出的,您的“限制”子句可能会导致结果集没有明确定义。

【讨论】:

我认为我对索引不太擅长,但我还没有索引 sec_stat_sec_name 和 sec_stat_date 在我的架构中查看 KEY sec_stat_date (sec_stat_date)、KEY sec_stat_sec_name ( sec_stat_sec_name) 您需要一个以正确顺序包含多个字段的索引。两个独立的索引不会有太大帮助。同时运行“解释选择”以查看正在使用的索引。

以上是关于在具有数百万条记录的 2 个表上加入更快的主要内容,如果未能解决你的问题,请参考以下文章

mysql如何更快地插入数百万条记录? [关闭]

具有数百万条记录的 2 个数据帧之间的 Pyspark 交叉连接

删除数百万条记录 oracle [关闭]

处理具有数百万条记录更新和大量读数的 MySQL 表的最佳方法

如何从 db2 表中删除数百万条记录

即使使用 parallel(8) 提示,具有数百万条记录的表中的 Count(1) 也很慢