【问题标题】:Slow joins, index not being used on a varchar column慢速连接,未在 varchar 列上使用索引
【发布时间】:2018-02-03 10:29:24
【问题描述】:

我有两个表,它们之间的连接需要很长时间。创建了相关索引,但显然它没有被使用是我的猜测。

表 1:

CREATE TABLE `INTRADAY_PRICES_CASH` (
`TradingSymbol` varchar(100) CHARACTER SET latin1 NOT NULL,
`SnapshotDateTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE 
CURRENT_TIMESTAMP,
`Open` double NOT NULL,
`Low` double NOT NULL,
`High` double NOT NULL,
`Close` double NOT NULL,
`Volume` double NOT NULL,
`SnapshotDate` date NOT NULL,
`SnapshotTime` time NOT NULL,
 `UpdateToDBTime` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
 PRIMARY KEY (`TradingSymbol`,`SnapshotDateTime`),
 KEY `IDX_SNAPSHOTDATE` (`SnapshotDate`),
 KEY `IDX_SNAPSHOTDATETIME` (`SnapshotDateTime`),
 KEY `IDX_SNAPSHOTTIME` (`SnapshotTime`),
 KEY `IDX_TRADINGSYMBOL` (`TradingSymbol`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

表 2:

CREATE TABLE `ACTIVE_INSTRUMENTS_CASH` (
  `INSTRUMENT_ID` bigint(20) NOT NULL AUTO_INCREMENT,
  `INSTRUMENT_TOKEN` bigint(20) DEFAULT NULL,
  `EXCHANGE_TOKEN` bigint(20) DEFAULT NULL,
  `TRADING_SYMBOL` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
  `INSTRUMENT_NAME` varchar(500) COLLATE utf8_unicode_ci DEFAULT NULL,
  `EXPIRY` date DEFAULT NULL,
  `LOT_SIZE` double DEFAULT NULL,
  `TICK_SIZE` float DEFAULT NULL,
  `INSTRUMENT_TYPE` varbinary(10) DEFAULT NULL,
  `SEGMENT` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
  `EXCHANGE` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
  `AS_ON_DATE` date NOT NULL,
  PRIMARY KEY (`INSTRUMENT_ID`),
  UNIQUE KEY `IND_AS_ON_DATE` (`AS_ON_DATE`,`TRADING_SYMBOL`),
  KEY `IND1` (`AS_ON_DATE`),
  KEY `IND2` (`INSTRUMENT_TOKEN`),
  KEY `IND3` (`TRADING_SYMBOL`),
  KEY `IND4` (`INSTRUMENT_TYPE`)
) ENGINE=InnoDB AUTO_INCREMENT=196606 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

表 2 有 TRADING_SYMBOLAS_ON_DATE 作为 PK。对于一个日期,此表中可能有多个交易品种。

在表 1 中,对于相同的交易代码和日期组合,我们将有多行表示该代码在同一日期的不同分钟内的不同价格。

现在,我想加入这些表格,以了解有多少行与两个表格中的交易代码和日期组合匹配。

SELECT COUNT(*) FROM INTRADAY_PRICES_CASH C, ACTIVE_INSTRUMENTS_CASH I
WHERE C.`SnapshotDate`>'2017-08-14'
AND I.`TRADING_SYMBOL`=C.`TradingSymbol`
AND I.`AS_ON_DATE`=C.`SnapshotDate`

说明它正在使用:

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  I   range   IND_AS_ON_DATE,IND1,IND3    IND_AS_ON_DATE  3   \N  15066   Using where; Using index
1   SIMPLE  C   ref IDX_SNAPSHOTDATE    IDX_SNAPSHOTDATE    3   u754793479_stock.I.AS_ON_DATE   771 Using where; Using index

奇怪的是,我没有看到它使用与 TradingSymbol 相关的两个表中的任何指数。这可能会导致延迟。

这种理解是否正确,如果正确,如何纠正以使其使用与交易品种有关的索引。我是否需要交易品种的全文索引才能使用它。

编辑

以下是一些额外的说明:

INTRADAY_PRICES_CASH 包含 69700675 行。 ACTIVE_INSTRUMENTS_CASH 包含 190177 行。

SELECT COUNT(*) FROM INTRADAY_PRICES_CASH C WHERE C.SnapshotDate>'2017-08-14' 返回 3911679 行

我的托管服务提供商没有提供对 innodb_index_stats 的访问权限。因此无法触发此查询: SELECT *, stat_value * @@innodb_page_size FROM mysql.innodb_index_stats WHERE table_name = 'INTRADAY_PRICES_CASH' and stat_name = 'size' and indexname = 'IDX_SNAPSHOTDATETRADINGSYMBOL';

select @@innodb_buffer_pool_size 是 133.2 G

我有 16GB 内存。

【问题讨论】:

  • 您需要额外的索引INTRADAY_PRICES_CASH(SnapshotDate, TradingSymbol)ACTIVE_INSTRUMENTS_CASH(TRADING_SYMBOL, AS_ON_DATE)。列的顺序很重要,目前您的两个表中的 2 列索引不同。
  • 谢谢。两者都加了。虽然解释显示它正在使用 INTRADAY_PRICES_CASH(SnapshotDate, TradingSymbol),但仍然需要 36 分钟。这些是索引:KEY IND5` (TRADING_SYMBOL,AS_ON_DATE)` 和 KEY IDX_SNAPSHOTDATETRADINGSYMBOL` (SnapshotDate,TradingSymbol)`。说明显示:表类型可能键键 I 范围 IND_AS_ON_DATE,IND1,IND3,IND5 IND_AS_ON_DATE C ref IDX_SNAPSHOTDATE,IDX_SNAPSHOTDATETRADINGSYMBOL IDX_SNAPSHOTDATETRADINGSYMBOL
  • 你得到的计数是多少,你的表中有多少行?这是一个非常简单的查询,可以完全从索引中计算出来,即使您的表中有十亿行,也不应该花费 36 分钟。 TradingSymbol 包含/是否包含更可能 10 个或更可能 100 个字符?将其替换为表示该符号的 int 可以提高速度(相对于符号的平均长度)。
  • INTRADAY_PRICES_CASH 包含 69700675 行。 ACTIVE_INSTRUMENTS_CASH 包含 190177 行。我会检查字符集然后回来。 TradingSymbol 包含代表股票代码的字符...如 APPLE、AMAZON 等。
  • 啊。我刚刚看到是什么原因造成的。 tradingSymbolTRADING_SYMBOL 都需要具有相同的字符集/排序规则,因此请替换一个(您可能应该使用 utf8 以使其最灵活)。如前所述,更可取的是用 int 替换它,但修复字符集更容易,并且应该已经带来了很大的改进。

标签: mysql join indexing fulltext-index


【解决方案1】:

请选择更好的别名 -- 两个表都有初始 C 和 I。

请使用首选的JOIN...ON

SELECT  COUNT(*)
    FROM  INTRADAY_PRICES_CASH AS p
    JOIN  ACTIVE_INSTRUMENTS_CASH AS a
          ON  a.`TRADING_SYMBOL` = p.`TradingSymbol`
         AND  a.`AS_ON_DATE`     = p.`SnapshotDate` 
    WHERE  p.`SnapshotDate`>'2017-08-14'

注意key_len 是如何只有 3,这意味着它只使用了索引的 DATE 部分。

TRADING_SYMBOLTradingSymbol 使用相同 CHARACTER SETCOLLATION。 (建议您也将它们拼写相同。)不同的排序规则会阻止使用索引(对于该列)。

这不是给你同样的COUNT(*)吗?

SELECT  COUNT(*)
    FROM  INTRADAY_PRICES_CASH
    WHERE  p.`SnapshotDate`>'2017-08-14';

如果您从 15 日开始,为什么不说 >= ... 15 而不是 > ... 14?这将适用于 DATETIMEDATE

其他表是否使用INSTRUMENT_ID?或者是否可以删除该列并将UNIQUE 键提升为PRIMARY

如果你真的需要加入,那么我可能有其他索引建议。

【讨论】:

  • 感谢您的指点,让这一切变得更好。来自INTRADAY_PRICES_CASH 的计数给出了相同的计数。但我想确保我的下游查询仅处理来自INTRADAY_PRICES_CASH 的集合,这些查询在ACTIVE_INSTRUMENTS_CASH 中具有相同的表示。我已经按照@Solarflare 的建议更改了排序规则。性能有所提高,但仍需要 5 分钟才能运行
  • INTRADAY_PRICES_CASH 中有多少行?结果是什么COUNT(*)innodb_buffer_pool_size 的值是多少?你有多少内存?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-10-10
  • 2012-08-08
  • 1970-01-01
  • 2014-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多