为什么解释 SELECT 在 "key" 字段中显示不同的索引名称?

Why explain SELECT shows different index name in "key" field?

我的产品 table 有多个索引,products_published_at_category_id_regular_price_status_index 有 4 个字段

CREATE TABLE `products` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `creator_id` bigint unsigned NOT NULL,
  `title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `status` enum('D','P','A','I') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'D' COMMENT ' D => Draft, P=>Pending Review, A=>Active, I=>Inactive',
  `slug` varchar(260) COLLATE utf8mb4_unicode_ci NOT NULL,
  `sku` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
  `category_id` smallint unsigned NOT NULL,
  `city_id` smallint unsigned NOT NULL,
  `regular_price` decimal(9,2) DEFAULT NULL,
  `sale_price` decimal(9,2) DEFAULT NULL,
  `in_stock` tinyint(1) NOT NULL DEFAULT '0',
  `stock_qty` mediumint unsigned NOT NULL DEFAULT '0',
  `has_discount_price` tinyint(1) NOT NULL DEFAULT '0',
  `is_featured` tinyint(1) NOT NULL DEFAULT '0',
  `short_description` mediumtext COLLATE utf8mb4_unicode_ci,
  `description` longtext COLLATE utf8mb4_unicode_ci,
  `published_at` datetime DEFAULT NULL,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `products_creator_id_title_index` (`creator_id`,`title`),
  UNIQUE KEY `products_slug_unique` (`slug`),
  KEY `products_category_id_foreign` (`category_id`),
  KEY `products_city_id_foreign` (`city_id`),
  KEY `products_published_at_category_id_regular_price_status_index` (`published_at`,`category_id`,`regular_price`,`status`),
  KEY `products_published_category_sale_price_status_discount_index` (`published_at`,`category_id`,`sale_price`,`status`,`has_discount_price`),
  KEY `products_title_status_index` (`title`,`status`),
  KEY `products_published_at_title_status_index` (`published_at`,`title`,`status`),
  CONSTRAINT `products_category_id_foreign` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT,
  CONSTRAINT `products_city_id_foreign` FOREIGN KEY (`city_id`) REFERENCES `cities` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT,
  CONSTRAINT `products_creator_id_foreign` FOREIGN KEY (`creator_id`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

但是 运行宁 sql 在 sql 语句中使用这 4 个参数并查看解释

 explain format =json  SELECT *
    FROM `products`
    WHERE products.published_at >'2022-04-01'
      AND products.published_at <='2022-04-21'
      AND `products`.`category_id` = '6'
      AND `products`.`status` = 'A'
    ORDER BY `regular_price` asc

我在“关键”值中看到“products_category_id_foreign”,但没有像我预期的那样看到“products_published_at_category_id_regular_price_status_index”:

{
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "107.90"
    },
    "ordering_operation": {
      "using_filesort": true,
      "table": {
        "table_name": "products",
        "access_type": "ref",
        "possible_keys": [
          "products_category_id_foreign",
          "products_published_at_category_id_regular_price_status_index",
          "products_published_category_sale_price_status_discount_index",
          "products_published_at_title_status_index"
        ],
        "key": "products_category_id_foreign",
        "used_key_parts": [
          "category_id"
        ],
        "key_length": "2",
        "ref": [
          "const"
        ],
        "rows_examined_per_scan": 122,
        "rows_produced_per_join": 4,
        "filtered": "3.79",
        "cost_info": {
          "read_cost": "95.70",
          "eval_cost": "0.46",
          "prefix_cost": "107.90",
          "data_read_per_join": "11K"
        },
        "used_columns": [
          "id",
          "creator_id",
          "title",
          "status",
          "slug",
          "sku",
          "category_id",
          "city_id",
          "regular_price",
          "sale_price",
          "in_stock",
          "stock_qty",
          "has_discount_price",
          "is_featured",
          "short_description",
          "description",
          "published_at",
          "created_at",
          "updated_at"
        ],
        "attached_condition": "((`BiCurrencies`.`products`.`published_at` > TIMESTAMP'2022-04-01 00:00:00') and (`BiCurrencies`.`products`.`published_at` <= TIMESTAMP'2022-04-21 00:00:00') and (`BiCurrencies`.`products`.`status` = 'A'))"
      }
    }
  }
}

"used_key_parts=  [                    "category_id"                 ],

...

这么解释好像不太好

我还看到“query_cost”和“cost_info”中的值很高。 我想这些是一些内部 mysql 值,它们必须尽可能小。 我尝试 运行 命令:

OPTIMIZE TABLE products;
ANALYZE TABLE products;

在 运行宁我的 sql 声明之前 - 但结果是一样的...

版本 mysqlnd 8.1.4 under kubuntu 20

更新第 1 部分:

谢谢!

  1. 根据我之前在多个字段上创建索引时的经验,我遵循了下一条规则: 左侧必须是集合数量最多的字段。在我的例子中肯定是 published_at.

右侧必须是状态(4 个可能值的枚举)

category_id - 大约有 10 个可能的值。

字段published_at也可以用在“=”比较中,也用在“>=”/“<”中。为不同的情况创建不同的索引? 2) 尝试使用命令删除键 products_category_id_foreign :

alter TABLE `products` drop key products_category_id_foreign;

我收到错误:

SQL Error [1553] [HY000]: Cannot drop index 'products_category_id_foreign': needed in a foreign key constraint

这是参考其他 table...

  1. 能否在mysql请求中设置优选索引?其实我用的是laraveleloquent库?

更新第 2 部分:

  1. 我将 published_at 移动到我的过滤器的末尾并在数据库重建之后 应用了我需要的索引。 至于“一个古老的神话”。是的,看起来是真的。在接受您的回答之前,您能否参考一下 mysql 5.x/8.x 的工作规则? 我已经阅读了一些文档(包括官方 mysql 文档),但我没有阅读这样的规则。

  2. 至于错误消息,当我尝试删除 products_category_id_foreign 时:我使用 laravel 迁移和 eloquent 并且 products_category_id_foreign 是使用命令自动创建的:

    $table->unsignedSmallInteger('category_id')->unsigned(); $table->foreign('category_id')->references('id')->on('categories')->onUpdate('RESTRICT')->onDelete( 'CASCADE');

所以我不知道如何删除该索引。但似乎这不是问题,因为我设法使用了有效索引。

谢谢!

WHERE products.published_at >'2022-04-01'
  AND products.published_at <='2022-04-21'
  AND `products`.`category_id` = '6'
  AND `products`.`status` = 'A'

将“范围”放在首位通常是 counter-productive;将它移到最后,在所有“=”测试之后:

INDEX(category_id, status, published_at)

并删除所有匹配该索引开头的索引,即

KEY `products_category_id_foreign` (`category_id`),

旁注:“午夜”通常被认为是一天的开始:

WHERE products.published_at >= '2022-04-01'
  AND products.published_at  < '2022-04-21'

重新更新 2:

  • 参考手册关注可以做什么。我专注于应该做什么。因此,我认为参考手册中没有相关信息。
  • B+树(InnoDB 将其用于索引和数据)可以有效地扫描“连续”条目。
  • 作为一个思维练习,将复合索引想象成列值连接在一起,然后排序到 B+ 树中。这导致我建议将所有 = 测试列放在第一位,然后是一个“范围”测试 (published_at)。
  • 没有完全解决“古老的神话”:
  • 另见 http://mysql.rjweb.org/doc.php/index_cookbook_mysql and http://mysql.rjweb.org/doc.php/index1
  • Adding/removing 索引:参见关于 ALTER TABLE ... ADD INDEXDROP INDEX 的参考手册。也许 FOREIGN KEY 妨碍了。
  • 不要让我开始了解框架如何需要两次了解数据库——一次是为了框架的数据视图,另一次是为了底层引擎的视图 (MySQL)。