使用索引的慢MySQL查询

时间:2019-06-24 02:25:58

标签: mysql indexing

我有一个Providers表,如下所示:

| id  | lastName | firstName | middleName |
| --- | -------- | --------- | ---------- |

具有以下索引:

  • Providers_lastName
  • Providers_firstName
  • Providers_lastName_firstName
  • Providers_lastName_firstName_middleName

我所有的查询在lastName和firstName值中都使用尾随通配符:

SELECT * FROM Providers
WHERE lastName LIKE 'smi%'
ORDER BY lastName ASC, firstName ASC, middleName
LIMIT 0, 50
SELECT * FROM Providers
WHERE firstName LIKE 'mar%'
ORDER BY lastName ASC, firstName ASC, middleName
LIMIT 0, 50

此表中大约有700万行。我的lastName查询非常快。但是,firstName的速度非常慢。我在这里做错什么吗?在不更改或删除顺序的情况下,我还可以添加其他哪些索引来提高我的仅用于名字的查询的性能?

编辑1:

EXPLAIN查询的

lastName输出:

{
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "69901.30"
    },
    "ordering_operation": {
      "using_filesort": false,
      "table": {
        "table_name": "Providers",
        "access_type": "range",
        "possible_keys": [
          "Providers_lastName",
          "Providers_lastName_firstName",
          "Providers_lastName_firstName_middleName"
        ],
        "key": "Providers_lastName_firstName_middleName",
        "used_key_parts": [
          "lastName"
        ],
        "key_length": "143",
        "rows_examined_per_scan": 59008,
        "rows_produced_per_join": 59008,
        "filtered": "100.00",
        "index_condition": "(`db_name`.`providers`.`lastName` like 'smi%')",
        "cost_info": {
          "read_cost": "64000.51",
          "eval_cost": "5900.80",
          "prefix_cost": "69901.31",
          "data_read_per_join": "158M"
        },
        "used_columns": [
          "id",
          "firstName",
          "middleName",
          "lastName",
          // OTHER COLUMNS
        ]
      }
    }
  }
}
EXPLAIN查询的

firstName输出:

{
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "390813.95"
    },
    "ordering_operation": {
      "using_filesort": false,
      "table": {
        "table_name": "Providers",
        "access_type": "index",
        "possible_keys": [
          "Providers_firstName"
        ],
        "key": "Providers_lastName_firstName_middleName",
        "used_key_parts": [
          "lastName",
          "firstName",
          "middleName"
        ],
        "key_length": "309",
        "rows_examined_per_scan": 948,
        "rows_produced_per_join": 329914,
        "filtered": "5.27",
        "cost_info": {
          "read_cost": "357822.55",
          "eval_cost": "32991.40",
          "prefix_cost": "390813.95",
          "data_read_per_join": "883M"
        },
        "used_columns": [
          "id",
          "firstName",
          "middleName",
          "lastName",
          // OTHER COLUMNS
        ],
        "attached_condition": "(`db_name`.`providers`.`firstName` like 'mar%')"
      }
    }
  }
}

SHOW CREATE TABLE

CREATE TABLE `Providers` (
  `id` varchar(10) NOT NULL,
  `firstName` varchar(20) DEFAULT NULL,
  `middleName` varchar(20) DEFAULT NULL,
  `lastName` varchar(35) DEFAULT NULL,
  /* Other columns */
  PRIMARY KEY (`id`),
  KEY `Providers_firstName` (`firstName`),
  KEY `Providers_lastName` (`lastName`),
  KEY `Providers_lastName_firstName` (`lastName`,`firstName`),
  KEY `Providers_lastName_firstName_middleName` (`lastName`,`firstName`,`middleName`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

编辑2:

运行SHOW SESSION STATUS LIKE 'Handler%'后输出FLUSH STATUS

查询1(名字):

{
    "data":
    [
        {
            "Variable_name": "Handler_commit",
            "Value": "1"
        },
        {
            "Variable_name": "Handler_delete",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_discover",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_external_lock",
            "Value": "2"
        },
        {
            "Variable_name": "Handler_mrr_init",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_prepare",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_first",
            "Value": "1"
        },
        {
            "Variable_name": "Handler_read_key",
            "Value": "1"
        },
        {
            "Variable_name": "Handler_read_last",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_next",
            "Value": "1487176"
        },
        {
            "Variable_name": "Handler_read_prev",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_rnd",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_rnd_next",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_rollback",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_savepoint",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_savepoint_rollback",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_update",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_write",
            "Value": "0"
        }
    ]
}

查询2(姓):

{
    "data":
    [
        {
            "Variable_name": "Handler_commit",
            "Value": "1"
        },
        {
            "Variable_name": "Handler_delete",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_discover",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_external_lock",
            "Value": "2"
        },
        {
            "Variable_name": "Handler_mrr_init",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_prepare",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_first",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_key",
            "Value": "1"
        },
        {
            "Variable_name": "Handler_read_last",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_next",
            "Value": "49"
        },
        {
            "Variable_name": "Handler_read_prev",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_rnd",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_rnd_next",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_rollback",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_savepoint",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_savepoint_rollback",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_update",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_write",
            "Value": "0"
        }
    ]
}

编辑3

使用FORCE_INDEX(Providers_firstName)

EXPLAIN查询的

firstName输出:

{
    "query_block": {
      "select_id": 1,
      "cost_info": {
        "query_cost": "389514.60"
      },
    "ordering_operation": {
        "using_filesort": true,
        "table": {
          "table_name": "Providers",
          "access_type": "range",
          "possible_keys": [
            "Providers_firstName"
          ],
          "key": "Providers_firstName",
          "used_key_parts": [
            "firstName"
          ],
          "key_length": "83",
          "rows_examined_per_scan": 329914,
          "rows_produced_per_join": 329914,
          "filtered": "100.00",
          "index_condition": "(`db_name`.`providers`.`firstName` like 'mar%')",
          "cost_info": {
            "read_cost": "356523.20",
            "eval_cost": "32991.40",
            "prefix_cost": "389514.60",
            "data_read_per_join": "883M"
          },
        "used_columns": [
            "id",
            "firstName",
            "middleName",
            "lastName",
            // Other columns
          ]
      }
    }
  }
}

处理程序计数:

{
    "data":
    [
        {
            "Variable_name": "Handler_commit",
            "Value": "1"
        },
        {
            "Variable_name": "Handler_delete",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_discover",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_external_lock",
            "Value": "2"
        },
        {
            "Variable_name": "Handler_mrr_init",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_prepare",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_first",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_key",
            "Value": "51"
        },
        {
            "Variable_name": "Handler_read_last",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_next",
            "Value": "168497"
        },
        {
            "Variable_name": "Handler_read_prev",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_rnd",
            "Value": "50"
        },
        {
            "Variable_name": "Handler_read_rnd_next",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_rollback",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_savepoint",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_savepoint_rollback",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_update",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_write",
            "Value": "0"
        }
    ]
}

1 个答案:

答案 0 :(得分:0)

查询1

WHERE lastName LIKE 'smi%'
ORDER BY lastName ASC, firstName ASC, middleName

可能使用此索引。 (请提供EXPLAIN...):

Providers_lastName_firstName_middleName

之所以有效,是因为它可以遍历索引的smi...部分。

我假设SELECT *仅提取4列,而idPRIMARY KEY?而Providers_lastName_firstName_middleNameINDEX(lastName, firstName, middleName),最后加上一个隐式id,因为它是InnoDB ??

这意味着整个查询都可以在索引中运行。 EXPLAIN可以通过说“使用索引”来确认这一点,这意味着“覆盖索引”。

此外,此查询仅涉及50行-因为索引已针对WHEREORDER BY进行了很好的调整,因此实际上也可以折叠在LIMIT 50中。

查询2

WHERE firstName LIKE 'mar%'
ORDER BY lastName ASC, firstName ASC, middleName

Providers_firstName

还可以遍历mar...的索引,但随后必须遍历数据才能获取其余的列。

但是其余所有优化(覆盖等)均不适用。您可以添加INDEX(first, last, middle, id)使其快速。

此查询无法在LIMIT中折叠。

注释

在美国,名字的10%以最常见的字母“ S”开头。 (“ 10%”在全球范围内大致相同,只是最受欢迎的字母可能有所不同。)

优化器有多种方法来执行任何查询,并根据有限的信息选择“最佳”。当很明显一个范围将是一个大范围(WHERE lastName LIKE 'S%')时,它可以选择从使用索引切换为简单地丢弃许多行。我不认为这是在这里发生的,但是EXPLAIN会再次告诉我们。

有关创建最佳索引的更多信息:http://mysql.rjweb.org/doc.php/index_cookbook_mysql

解释后

如果我正确阅读了EXPLAINs,它们都使用INDEX(last, first, middle),从而避免了排序。还请注意"using_filesort": false。,这允许查询在LIMIT 50之后停止。

要收集更多信息,请运行以下命令:

FLUSH STATUS;
SELECT ...
SHOW SESSION STATUS LIKE 'Handler%';

如果Handler_write*0,则没有排序。同时,被触摸的Handler_read* values gives you the number of rows (probably in the INDEX`)的总和。

我希望查询1总共显示50个读操作,因为它(理论上)可以深入到smi处的索引中,并获取下50个(或更少)行。这应该花费几毫秒。

查询2更加混乱,因为在查找具有该名字的50之前,它将需要扫描大量索引。不会是7M,但可能是5万行。如果索引的必要部分已缓存,则可能需要花费几秒钟的时间。或几分钟(如果没有)。

没有办法使Q2快于Q1。对于mar%,此可能更快,而对于m%INDEX(first, last, middle),此可能更慢。也就是说,引入这样的索引是有风险的。

在大多数情况下,如果您也有INDEX(a),则INDEX(a,b)是多余的。也就是说,您可能有两个可以删除的索引。