mysql - 按 id 排序时非常慢,但按时间戳、id 排序时速度很快

标签 mysql indexing sql-order-by primary-key

我遇到了一个非常令人费解的优化案例。我不是 SQL 专家,但这个案例似乎仍然违背了我对集群关键原则的理解。

我有下表架构:

CREATE TABLE `orders` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `chargeQuote` tinyint(1) NOT NULL,
  `features` int(11) NOT NULL,
  `sequenceIndex` int(11) NOT NULL,
  `createdAt` bigint(20) NOT NULL,
  `previousSeqId` bigint(20) NOT NULL,
  `refOrderId` bigint(20) NOT NULL,
  `refSeqId` bigint(20) NOT NULL,
  `seqId` bigint(20) NOT NULL,
  `updatedAt` bigint(20) NOT NULL,
  `userId` bigint(20) NOT NULL,
  `version` bigint(20) NOT NULL,
  `amount` decimal(36,18) NOT NULL,
  `fee` decimal(36,18) NOT NULL,
  `filledAmount` decimal(36,18) NOT NULL,
  `makerFeeRate` decimal(36,18) NOT NULL,
  `price` decimal(36,18) NOT NULL,
  `takerFeeRate` decimal(36,18) NOT NULL,
  `triggerOn` decimal(36,18) NOT NULL,
  `source` varchar(32) NOT NULL,
  `status` varchar(50) NOT NULL,
  `symbol` varchar(32) NOT NULL,
  `type` varchar(50) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `IDX_STATUS` (`status`) USING BTREE,
  KEY `IDX_USERID_SYMBOL_STATUS_TYPE` (`userId`,`symbol`,`status`,`type`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=7937243 DEFAULT CHARSET=utf8mb4;

这是一张大 table 。 1 亿行。它已由 createdAt 进行分片,因此 1 亿 = 1 个月的订单值(value)。

我有一个慢速查询。查询非常简单:

select id,chargeQuote,features,sequenceIndex,createdAt,previousSeqId,refOrderId,refSeqId,seqId,updatedAt,userId,version,amount,fee,filledAmount,makerFeeRate,price,takerFeeRate,triggerOn,source,`status`,symbol,type
from orders where 1=1
and userId=100000
and createdAt >= '1567775174000' and createdAt <= '1567947974000'
and symbol in ( 'BTC_USDT' )
and status in ( 'FULLY_FILLED' , 'PARTIAL_CANCELLED' , 'FULLY_CANCELLED' )
and type in ( 'BUY_LIMIT' , 'BUY_MARKET' , 'SELL_LIMIT' , 'SELL_MARKET' )
order by id desc limit 0,20;

此查询需要 24 秒。满足userId=100000的行数很少,大约为100。而满足整个where子句的行数为0。

但是当我做了一个小调整时,即我更改了 order by 子句:

order by id desc limit 0,20; -- before
order by createdAt desc, id desc limit 0,20; -- after

它变得非常快,0.03秒。

我可以看到它在 MySQL 引擎中产生了很大的差异,因为 explain 表明,在更改之前它使用 key: PRIMARY ,而在它最终使用 之后键:IDX_USERID_SYMBOL_STATUS_TYPE,正如预期的那样,我想因此非常快。以下是解释计划:

select_type table   partitions  type    possible_keys   key key_len ref rows    filtered    Extra
SIMPLE  orders      index   IDX_STATUS,IDX_USERID_SYMBOL_STATUS_TYPE    PRIMARY 8       20360   0.02    Using where
SIMPLE  orders      range   IDX_STATUS,IDX_USERID_SYMBOL_STATUS_TYPE    IDX_USERID_SYMBOL_STATUS_TYPE   542     26220   11.11   Using index condition; Using where; Using filesort

那么什么给出了呢?实际上,我对它不是按 id(主键)自然排序这一事实感到非常惊讶。这不是MySQL中的聚集键吗?为什么它在按 id 排序时选择不使用索引?

我很困惑,因为要求更高的查询(按 2 个条件排序)速度非常快,但更宽松的查询却很慢。

不,我尝试了分析表订单;但什么也没发生。

最佳答案

MySQL 对于使用 ORDER BY ... LIMIT n 的查询有两种替代查询计划:

  1. 读取所有符合条件的行,对它们进行排序,然后选择前 n 行。
  2. 按排序顺序读取行,并在找到 n 个符合条件的行时停止。

为了决定哪个是更好的选项,优化器需要估计 WHERE 条件的过滤效果。这并不简单,特别是对于未索引的列或值相关的列。在你的例子中,MySQL 优化器显然认为第二种策略是最好的。换句话说,它并没有看到任何行都不会满足 WHERE 子句,而是认为有 2% 的行满足 WHERE 子句,并且只扫描部分行就能找到 20 行。表按主键顺序向后。

如何估计 WHERE 子句的过滤效果在 5.6、5.7 和 8.0 之间存在很大差异。如果您使用的是 MySQL 8.0,您可以尝试为涉及的列创建直方图,看看是否可以改进估计。如果没有,我认为您唯一的选择是使用 FORCE INDEX 提示来使优化器选择所需的索引。

对于快速查询,第二种策略不是一个选项,因为createdAt上没有可用于避免排序的索引。

更新: 阅读 Rick 的回答,我意识到仅对 userId 建立索引应该可以加快您的 ORDER BY id 查询速度。在这样的索引中,给定 userId 的条目将按主键排序。因此,使用此索引既可以仅访问所请求的 userId 的行,也可以访问按请求的排序顺序(按 id)的行。

关于mysql - 按 id 排序时非常慢,但按时间戳、id 排序时速度很快,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57839845/

相关文章:

PHP 和/或 mysql : Make a date from some variables

postgresql - 估计 Postgres 索引的大小

sql - 批量插入表时优化索引更新速度

MYSQL - 排序依据和限制 : Different result when using variable or constant

java - mysql代码的备份数据库不起作用

php - 无法让 PHP mysql 返回最高值

mysql - 如果在查询中使用 order by,如何对 MySQL 中的索引列进行排序?

php - MySql 按字段排序最后上传的第一个在有序 ID 的前面

mysql - 使用 zend 框架编写 mysql 查询

mysql - 数据中的换行符和回车符 : 0D 0A