【问题标题】:Avoid table scan and use index instead in query避免表扫描并在查询中使用索引
【发布时间】:2019-10-14 09:06:57
【问题描述】:

我正在设计一个新的数据库,并注意到我的查询没有像应有的那样扩展。当我的聚合涉及数百条记录时,我发现响应时间显着增加。我想知道我的查询是否存在严重缺陷,或者我只是没有使用正确的索引。

我对我的查询做了很多调整,但还没有想出一种方法来消除全表扫描,而是使用索引。当我在查询中使用类似于 EXPLAIN 的工具时,我看到以下内容:

  • 全表扫描通常效率低下,请避免使用它们。
  • 您的查询使用 MySQL 的“文件排序”操作。这往往会减慢查询速度。
  • 您的查询使用 MySQL 的临时表。这可能需要额外的 I/O,并且往往会减慢查询速度。

表:

CREATE TABLE `indexTable` (
  `id` int(10) unsigned NOT NULL,
  `userId` int(10) unsigned NOT NULL,
  `col1` varbinary(320) NOT NULL,
  `col2` tinyint(3) unsigned NOT NULL,
  `col3` tinyint(3) unsigned NOT NULL,
  `createdAt` bigint(20) unsigned NOT NULL,
  `updatedAt` bigint(20) unsigned NOT NULL,
  `metadata` json NOT NULL,
  PRIMARY KEY (`id`,`userId`,`col1`,`col2`,`col3`),
  KEY `createdAt` (`createdAt`),
  KEY `id_userId_col1_col2_createdAt` (`id`,`userId`,`col1`,`col2`,`createdAt`),
  KEY `col1_col2_createdAt` (`col1`,`col2`,`createdAt`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8

查询:

SELECT t1.id, t1.userId, t1.col1, t1.col2, t1.col3, t1.metadata
FROM indexTable as t1
INNER JOIN(
    SELECT col1, col2, MAX(createdAt) AS maxCreatedAt
    FROM indexTable
    WHERE id = ? AND userId = ?
    GROUP BY col1, col2
    ORDER BY maxCreatedAt
    LIMIT 10 OFFSET 0) AS sub
ON t1.col1 = sub.col1
AND t1.col2 = sub.col2
AND t1.createdAt = sub.maxCreatedAt
WHERE t1.id = ? AND t1.userId = ?
ORDER BY t1.createdAt;

PK:id, userId, col1, col2, col3 索引:createdAt

解释:

{
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "34.50"
    },
    "ordering_operation": {
      "using_temporary_table": true,
      "using_filesort": true,
      "cost_info": {
        "sort_cost": "10.00"
      },
      "nested_loop": [
        {
          "table": {
            "table_name": "sub",
            "access_type": "ALL",
            "rows_examined_per_scan": 10,
            "rows_produced_per_join": 10,
            "filtered": "100.00",
            "cost_info": {
              "read_cost": "10.50",
              "eval_cost": "2.00",
              "prefix_cost": "12.50",
              "data_read_per_join": "3K"
            },
            "used_columns": [
              "col1",
              "col2",
              "maxCreatedAt"
            ],
            "attached_condition": "(`sub`.`maxCreatedAt` is not null)",
            "materialized_from_subquery": {
              "using_temporary_table": true,
              "dependent": false,
              "cacheable": true,
              "query_block": {
                "select_id": 2,
                "cost_info": {
                  "query_cost": "10.27"
                },
                "ordering_operation": {
                  "using_filesort": true,
                  "grouping_operation": {
                    "using_temporary_table": true,
                    "using_filesort": false,
                    "table": {
                      "table_name": "indexTable",
                      "access_type": "ref",
                      "possible_keys": [
                        "PRIMARY",
                        "createdAt",
                        "id_userId_col1_col2_createdAt",
                        "col1_col2_createdAt"
                      ],
                      "key": "PRIMARY",
                      "used_key_parts": [
                        "id",
                        "userId"
                      ],
                      "key_length": "8",
                      "ref": [
                        "const",
                        "const"
                      ],
                      "rows_examined_per_scan": 46,
                      "rows_produced_per_join": 46,
                      "filtered": "100.00",
                      "cost_info": {
                        "read_cost": "1.07",
                        "eval_cost": "9.20",
                        "prefix_cost": "10.27",
                        "data_read_per_join": "16K"
                      },
                      "used_columns": [
                        "id",
                        "userId",
                        "createdAt",
                        "col1",
                        "col2",
                        "col3"
                      ],
                      "attached_condition": "((`MyDB`.`indexTable`.`id` <=> 53) and (`MyDB`.`indexTable`.`userId` <=> 549814))"
                    }
                  }
                }
              }
            }
          }
        },
        {
          "table": {
            "table_name": "t1",
            "access_type": "ref",
            "possible_keys": [
              "PRIMARY",
              "createdAt",
              "id_userId_col1_col2_createdAt",
              "col1_col2_createdAt"
            ],
            "key": "id_userId_col1_col2_createdAt",
            "used_key_parts": [
              "id",
              "userId",
              "col1",
              "col2",
              "createdAt"
            ],
            "key_length": "339",
            "ref": [
              "const",
              "const",
              "sub.col1",
              "sub.col2",
              "sub.maxCreatedAt"
            ],
            "rows_examined_per_scan": 1,
            "rows_produced_per_join": 10,
            "filtered": "100.00",
            "cost_info": {
              "read_cost": "10.00",
              "eval_cost": "2.00",
              "prefix_cost": "24.50",
              "data_read_per_join": "3K"
            },
            "used_columns": [
              "id",
              "userId",
              "createdAt",
              "updatedAt",
              "col1",
              "col2",
              "col3",
              "metadata",
            ]
          }
        }
      ]
    }
  }
}

此查询在col1col2 的分组中查找最新记录,按createdAt 排序,并将条目限制为10 个。

【问题讨论】:

  • 尝试在(id, userId, col1, col2, createdAt)上创建索引。
  • 根据数据的分区方式,(id, userId, createdAt) 的复合索引也可能有效。
  • 我尝试了上述索引并没有看到任何改进。事实上,查询似乎执行得较慢。
  • 与所有相关表的 SHOW CREATE TABLE 语句一样,有关查询性能的问题需要对给定查询的 EXPLAIN
  • 您尝试过 FORCE INDEX 吗?有时MYSQL不使用索引,而是使用全表扫描。

标签: mysql indexing vitess


【解决方案1】:

“派生”表(子查询)需要这个复合索引:

INDEX(id, userid,  -- in either order
      col1, col2,  -- in this order
      createdAt)   -- to make it "covering"

使用该索引,可能不会进行全表扫描。但是,它涉及文件排序。这是因为ORDER BYGROUP BY 不同,它是一个聚合。

t1需要

INDEX(col1, col2,  -- in either order
      createdAt)

sub,maxCreatedAt -- 错字??

ORDER BY t1.createdAt -- 另一个必要的文件排序。

不要提防文件排序。特别是当只有 10 行时(如第二种情况)。

没有看到SHOW CREATE TABLE,我不能说“文件排序”和“临时表”是完全触及磁盘,还是在RAM中完成。

FORCE INDEX 几乎总是一个坏主意——即使今天有帮助,明天也可能会受到伤害。

如果需要查看太多的表,优化器会故意(并且正确地)使用表扫描——它比在索引和数据之间跳转要快。

【讨论】:

  • 嗨瑞克感谢您的回复。我刚刚发布了我的解释,希望对您有所帮助。我按照您的建议添加了两个索引,但看起来 sql 决定不使用它们。除了按照您所说的那样强制索引之外,还有什么想法不好?
  • @rmw - 请提供SHOW CREATE TABLE -- 您的措辞似乎与EXPLAIN 不一致。
  • 我混淆了一些列名,因为我想让它们保密。希望它不会太令人困惑,我只是尝试用更通用的列名替换一些列名。
  • 可能是优化器从选择中选择错误索引的情况。 子查询上的 `FORCE INDEX(id_userId_col1_col2_createdAt) 会发生什么?
  • 即使强制索引也无法正常工作。我将发布我是如何解决这个问题的,但感谢您的 idex 应该起作用的帮助!
【解决方案2】:

我能够通过更新我的查询以在GROUP BY 中包含iduserId 来解决此问题。然后我能够加入另外两个列,并且由于某种原因使 MySQL 使用了正确的索引。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-19
    • 1970-01-01
    • 1970-01-01
    • 2017-09-07
    • 1970-01-01
    相关资源
    最近更新 更多