【问题标题】:MySQL Order By Issue on large table大表上的 MySQL 按问题排序
【发布时间】:2016-10-18 09:49:29
【问题描述】:

我有以下mysql命令:

SELECT e.sIndex01 
FROM e_entity e 
WHERE e.meta_oid=336799 
ORDER BY e.sIndex02
LIMIT 100 OFFSET 0

这是桌子:

CREATE TABLE `e_entity` (
  `OID` int(11) NOT NULL AUTO_INCREMENT,
  `E_E_OID` int(11) DEFAULT NULL,
  `UNIQUE_IDX` int(11) NOT NULL,
  `APP_OID` int(11) NOT NULL,
  `META_OID` int(11) NOT NULL,
  `STORE_DATE` datetime NOT NULL,
  `REL_DISPLAY` varchar(1024) NOT NULL,
  `SINDEX01` varchar(1024) NOT NULL,
  `SINDEX02` varchar(1024) NOT NULL,
  `SINDEX03` varchar(1024) NOT NULL,
  `SINDEX04` varchar(1024) NOT NULL,
  `SINDEX05` varchar(1024) NOT NULL,
  `SINDEX06` varchar(1024) NOT NULL,
  `SINDEX07` varchar(1024) NOT NULL,
  `SINDEX08` varchar(1024) NOT NULL,
  `SINDEX09` varchar(1024) NOT NULL,
  `SINDEX10` varchar(1024) NOT NULL,
  `SINDEX11` varchar(1024) NOT NULL,
  `SINDEX12` varchar(1024) NOT NULL,
  `SINDEX13` varchar(1024) NOT NULL,
  `SINDEX14` varchar(1024) NOT NULL,
  `SINDEX15` varchar(1024) NOT NULL,
  `SINDEX16` varchar(1024) NOT NULL,
  `SINDEX17` varchar(1024) NOT NULL,
  `SINDEX18` varchar(1024) NOT NULL,
  `SINDEX19` varchar(1024) NOT NULL,
  `SINDEX20` varchar(1024) NOT NULL,
  `NINDEX01` double NOT NULL,
  `NINDEX02` double NOT NULL,
  `NINDEX03` double NOT NULL,
  `NINDEX04` double NOT NULL,
  `NINDEX05` double NOT NULL,
  `NINDEX06` double NOT NULL,
  `NINDEX07` double NOT NULL,
  `NINDEX08` double NOT NULL,
  `NINDEX09` double NOT NULL,
  `NINDEX10` double NOT NULL,
  `DINDEX01` datetime NOT NULL,
  `DINDEX02` datetime NOT NULL,
  `DINDEX03` datetime NOT NULL,
  `DINDEX04` datetime NOT NULL,
  `DINDEX05` datetime NOT NULL,
  `DINDEX06` datetime NOT NULL,
  `DINDEX07` datetime NOT NULL,
  `DINDEX08` datetime NOT NULL,
  `DINDEX09` datetime NOT NULL,
  `DINDEX10` datetime NOT NULL,
  `FREETEXT` mediumtext NOT NULL,
  `UID` int(11) DEFAULT NULL,
  PRIMARY KEY (`OID`),
  KEY `App_Parent` (`META_OID`),
  KEY `sindex01` (`META_OID`,`SINDEX01`(64)),
  KEY `sindex02` (`META_OID`,`SINDEX02`(64)),
  KEY `sindex03` (`META_OID`,`SINDEX03`(64)),
  KEY `sindex04` (`META_OID`,`SINDEX04`(64)),
  KEY `sindex05` (`META_OID`,`SINDEX05`(64)),
  KEY `sindex06` (`META_OID`,`SINDEX06`(64)),
  KEY `sindex07` (`META_OID`,`SINDEX07`(64)),
  KEY `sindex08` (`META_OID`,`SINDEX08`(64)),
  KEY `sindex09` (`META_OID`,`SINDEX09`(64)),
  KEY `sindex10` (`META_OID`,`SINDEX10`(64)),
  KEY `nindex01` (`META_OID`,`NINDEX01`),
  KEY `nindex02` (`META_OID`,`NINDEX02`),
  KEY `nindex03` (`META_OID`,`NINDEX03`),
  KEY `nindex04` (`META_OID`,`NINDEX04`),
  KEY `nindex05` (`META_OID`,`NINDEX05`),
  KEY `dindex01` (`META_OID`,`DINDEX01`),
  KEY `dindex02` (`META_OID`,`DINDEX02`),
  KEY `dindex03` (`META_OID`,`DINDEX03`),
  KEY `dindex04` (`META_OID`,`DINDEX04`),
  KEY `dindex05` (`META_OID`,`DINDEX05`),
  KEY `sindex11` (`META_OID`,`SINDEX11`(64)),
  KEY `sindex12` (`META_OID`,`SINDEX12`(64)),
  KEY `sindex13` (`META_OID`,`SINDEX13`(64)),
  KEY `sindex14` (`META_OID`,`SINDEX14`(64)),
  KEY `sindex15` (`META_OID`,`SINDEX15`(64)),
  KEY `sindex16` (`META_OID`,`SINDEX16`(64)),
  KEY `sindex17` (`META_OID`,`SINDEX17`(64)),
  KEY `sindex18` (`META_OID`,`SINDEX18`(64)),
  KEY `sindex19` (`META_OID`,`SINDEX19`(64)),
  KEY `sindex20` (`META_OID`,`SINDEX20`(64)),
  KEY `nindex06` (`META_OID`,`NINDEX06`),
  KEY `nindex07` (`META_OID`,`NINDEX07`),
  KEY `nindex08` (`META_OID`,`NINDEX08`),
  KEY `nindex09` (`META_OID`,`NINDEX09`),
  KEY `nindex10` (`META_OID`,`NINDEX10`),
  KEY `dindex06` (`META_OID`,`DINDEX06`),
  KEY `dindex07` (`META_OID`,`DINDEX07`),
  KEY `dindex08` (`META_OID`,`DINDEX08`),
  KEY `dindex09` (`META_OID`,`DINDEX09`),
  KEY `dindex10` (`META_OID`,`DINDEX10`),
  KEY `E_E_OID` (`E_E_OID`)
) ENGINE=InnoDB AUTO_INCREMENT=469158 DEFAULT CHARSET=utf8;

上面的查询需要几分钟才能完成,但是没有order by 子句只需要5 秒,所以order by 显然存在瓶颈。表中有 471000 行,我假设执行 order by 的匹配结果集是 171000 行。我可以遵循哪些建议来提高性能?

【问题讨论】:

  • 255 个字符是最大索引长度。但是你的 varchar 列的大小是 1024
  • @1000111 在 MySQL 5.0.3 之前可以将长度指定为 0 到 255 之间的值,在 5.0.3 及更高版本中可以指定为 0 到 65,535 的值。
  • 但是索引是由(META_OID,SINDEX01(64))组成的,最多75个字符
  • 跨列展开数组不好的原因有很多;拥有这么多索引也很糟糕。请详细说明这些列的意图是什么;我们或许能找到解决方法,但可能不涉及您当前采用的具体方法。
  • 另外,我看到了OFFSET;你打算通过行“分页”吗?

标签: mysql select sql-order-by innodb


【解决方案1】:

MySQL 不能使用你的前缀索引sIndex02order by,如documentation 中所述

在某些情况下,MySQL 不能使用索引来解析 ORDER BY,尽管它仍然使用索引来查找与 WHERE 子句匹配的行。这些案例包括:

  • 仅在 ORDER BY 子句中指定的列的前缀上存在索引。在这种情况下,索引不能用于完全解析排序顺序。例如,如果仅索引 CHAR(20) 列的前 10 个字节,则索引无法区分超过第 10 个字节的值,因此需要进行文件排序。

在应用limit 之前,文件排序需要从表中读取所有 171k 行。因此,为了加快查询速度,您必须让索引中的整个列都支持order by,如果不对您的表进行一些细微的修改,这是不可能的。

首先,启用配置选项innodb_large_prefix(如果你使用MySQL

然后将SINDEXxx-columns 的charmap 更改为每个字符使用1 个字节的任何内容(例如latin1),因为utf8 将使用(最多)3 个字节,因此几乎不会超过密钥大小限制, 或者,如果因为您在该列中实际上需要 utf8-characters 而无法做到这一点,请稍微减少您的列长度,例如1022.

如果所有这些都不可能(因为您需要 utf8 和 1024 的长度),您可以为每个长度为 1022 的 sindexxx 添加额外的列,添加触发器(或生成的列)存储前 1022 个字符,使用 META_OID 和那个新列和 order by 添加一个索引 - 假设你可以忍受不按最后 2 个字符排序。但由于您只有 171k 行,因此前 1022 个字符应该足够重要。

【讨论】:

  • 如果我重启mysql服务,innodb_large_prefix恢复到原来的值,会不会有什么问题?
  • 如果恢复,您的表将继续工作。但是,一旦您以任何方式更改表(例如添加一列),它就会将您的索引缩小到较小的大小(不询问,只显示警告),因此您应该在配置文件中设置该选项以使其永恒的。 innodb_file_format = BARRACUDA 也是如此,我忘了提及(并且是 innodb_large_prefix 的先决条件),您需要将表更改为 row_format=dynamic。使用它没有太大的缺点(它将成为 5.7 中的默认设置),除非您不能再不费吹灰之力地降级到 5.1。
  • 我用的是row_format=compressed 有什么大的不同吗?
  • @RafaelDiaz row_format=compressed 也可以,都支持长键。如果仅在 sindex01 上的索引运行得更快,则您不是在搜索 e.meta_oid=336799,而是在搜索例如e.meta_oid>336799,或者它没有使用order by 的第一个索引(例如,如果您没有减少到 1022 或更改为 latin1)。检查/发布您的explain。如果您只使用sindex01,则不必进行文件排序,它将遍历索引,读取每一行的表(!),直到找到meta_oid 的100 个条目(无需查找meta_oid通过索引)。在您的用例中(每个多行 ...
  • @RafaelDiaz 如果 mysql 使用索引,它必须在表中来回跳转才能读取通过索引找到的行。以这种方式读取 200k 行(来回跳跃)比一次性读取整个表从头到尾仅读取 500k 行要慢得多(这类似于硬盘的“碎片”)。一旦你获得了相当大比例的行(可能在 3% 到 10% 之间,但这取决于你的设置、系统和表),全表扫描比使用索引更快。尝试强制使用主键或使用ignore index
【解决方案2】:

您需要为 order by 和 where 子句创建非集群索引。请点击链接

https://dev.mysql.com/doc/refman/5.7/en/innodb-index-types.html

【讨论】:

  • 这个答案是无关紧要的。所有 InnoDB 辅助键都是“非集群的”。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-18
  • 1970-01-01
  • 2013-01-12
  • 2017-07-29
  • 1970-01-01
相关资源
最近更新 更多