为什么解释 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 部分:
谢谢!
- 根据我之前在多个字段上创建索引时的经验,我遵循了下一条规则:
左侧必须是集合数量最多的字段。在我的例子中肯定是 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...
- 能否在mysql请求中设置优选索引?其实我用的是laraveleloquent库?
更新第 2 部分:
我将 published_at 移动到我的过滤器的末尾并在数据库重建之后
应用了我需要的索引。
至于“一个古老的神话”。是的,看起来是真的。在接受您的回答之前,您能否参考一下
mysql 5.x/8.x 的工作规则?
我已经阅读了一些文档(包括官方 mysql 文档),但我没有阅读这样的规则。
至于错误消息,当我尝试删除 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 INDEX
和 DROP INDEX
的参考手册。也许 FOREIGN KEY
妨碍了。
- 不要让我开始了解框架如何需要两次了解数据库——一次是为了框架的数据视图,另一次是为了底层引擎的视图 (MySQL)。
我的产品 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 部分:
谢谢!
- 根据我之前在多个字段上创建索引时的经验,我遵循了下一条规则: 左侧必须是集合数量最多的字段。在我的例子中肯定是 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...
- 能否在mysql请求中设置优选索引?其实我用的是laraveleloquent库?
更新第 2 部分:
我将 published_at 移动到我的过滤器的末尾并在数据库重建之后 应用了我需要的索引。 至于“一个古老的神话”。是的,看起来是真的。在接受您的回答之前,您能否参考一下 mysql 5.x/8.x 的工作规则? 我已经阅读了一些文档(包括官方 mysql 文档),但我没有阅读这样的规则。
至于错误消息,当我尝试删除 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 INDEX
和DROP INDEX
的参考手册。也许FOREIGN KEY
妨碍了。 - 不要让我开始了解框架如何需要两次了解数据库——一次是为了框架的数据视图,另一次是为了底层引擎的视图 (MySQL)。