如何索引状态(标志)字段
How to index a status ( flag ) field
CREATE TABLE `products` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`brand` int(11) DEFAULT NULL,
`shown` tinyint(4) DEFAULT '1',
PRIMARY KEY (`id`),
KEY `fk_products_brandId_idx` (`brand`),
KEY `pk_products_shown` (`shown`),
CONSTRAINT `fk_products_brandId` FOREIGN KEY (`brand`) REFERENCES `brands` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `fk_products_categoryId` FOREIGN KEY (`category`) REFERENCES `categories` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
我有这个 "shown" 字段,我用它来标记已删除的记录(如果用户删除了一条记录,它并没有被删除;这些字段被设置为值 0)。
所以我所有的查询或多或少都是这样的:
"SELECT * FROM products WHERE brand = 1 AND shown = 1"
但如果我在 MySQL/oracle Workbench 中解释,它会说完整 table 扫描。显然这是有道理的,因为我 99% 的数据库记录都显示 = 1,但品牌也有索引。
这是 workbench
输出的解释
{
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "2.40"
},
"table": {
"table_name": "products",
"access_type": "ALL",
"possible_keys": [
"fk_products_brandId_idx",
"pk_products_shown"
],
"rows_examined_per_scan": 7,
"rows_produced_per_join": 2,
"filtered": "40.82",
"cost_info": {
"read_cost": "1.83",
"eval_cost": "0.57",
"prefix_cost": "2.40",
"data_read_per_join": "6K"
},
"used_columns": [
"id",
"name_bg",
"name_en",
"category",
"weight",
"width",
"length",
"height",
"brand",
"availability",
"text_bg",
"text_en",
"sold",
"price",
"price_retail",
"linked_products",
"created_at",
"created_by",
"updated_at",
"updated_by",
"shown"
],
"attached_condition": "((`whatever`.`products`.`shown` = 1) and (`whatever`.`products`.`brand` = 18))"
}
}
}
是我的逻辑错了,还是我显示的索引被修改了,所以正常的查询不会进行完整的 table 扫描?
mysql 5.7
在 (brand, shown)
上创建复合索引。该索引将使用 brand = constant AND shown=1
以及 brand=constant
.
上的过滤器加速查询
ALTER TABLE products ADD INDEX brand_shown (brand, shown);
这个索引也加速了
形式的查询
SELECT * FROM products WHERE brand >= 1 AND brand <=10 AND shown = 1;
或
SELECT * FROM products WHERE brand BETWEEN 1 AND 10 AND shown = 1;
做好索引需要一些知识。阅读 https://use-the-index-luke.com/ 是一种很好的学习方式。
MySQL 可能正在执行完整的 table 扫描,因为 shown
的基数(不同值的数量)非常低。所以它猜测 table 扫描比试图弄清楚如何从索引中获取数据要便宜。
如果您可以将查询修改为
SELECT name FROM products WHERE brand = constant AND shown = 1
然后将 name
添加到索引中,您将得到 a so-called covering index。这将允许 MySQL 仅通过对索引进行范围扫描来满足您的查询,并且速度会非常快。
ALTER TABLE products ADD INDEX brand_shown_name (brand, shown, name);
专业提示 避免在性能有问题的查询中使用 SELECT *
。相反,列出您需要的列。当您这样做时,MySQL 可能会应用一些优化(例如覆盖索引优化)。
专业提示 2 避免为了良好的衡量而创建索引。创建您实际需要的查询(或强制唯一性)。额外的索引占用 space 并减慢 INSERT 和 UPDATE 操作。
CREATE TABLE `products` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`brand` int(11) DEFAULT NULL,
`shown` tinyint(4) DEFAULT '1',
PRIMARY KEY (`id`),
KEY `fk_products_brandId_idx` (`brand`),
KEY `pk_products_shown` (`shown`),
CONSTRAINT `fk_products_brandId` FOREIGN KEY (`brand`) REFERENCES `brands` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `fk_products_categoryId` FOREIGN KEY (`category`) REFERENCES `categories` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
我有这个 "shown" 字段,我用它来标记已删除的记录(如果用户删除了一条记录,它并没有被删除;这些字段被设置为值 0)。
所以我所有的查询或多或少都是这样的:
"SELECT * FROM products WHERE brand = 1 AND shown = 1"
但如果我在 MySQL/oracle Workbench 中解释,它会说完整 table 扫描。显然这是有道理的,因为我 99% 的数据库记录都显示 = 1,但品牌也有索引。
这是 workbench
输出的解释{
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "2.40"
},
"table": {
"table_name": "products",
"access_type": "ALL",
"possible_keys": [
"fk_products_brandId_idx",
"pk_products_shown"
],
"rows_examined_per_scan": 7,
"rows_produced_per_join": 2,
"filtered": "40.82",
"cost_info": {
"read_cost": "1.83",
"eval_cost": "0.57",
"prefix_cost": "2.40",
"data_read_per_join": "6K"
},
"used_columns": [
"id",
"name_bg",
"name_en",
"category",
"weight",
"width",
"length",
"height",
"brand",
"availability",
"text_bg",
"text_en",
"sold",
"price",
"price_retail",
"linked_products",
"created_at",
"created_by",
"updated_at",
"updated_by",
"shown"
],
"attached_condition": "((`whatever`.`products`.`shown` = 1) and (`whatever`.`products`.`brand` = 18))"
}
}
}
是我的逻辑错了,还是我显示的索引被修改了,所以正常的查询不会进行完整的 table 扫描?
mysql 5.7
在 (brand, shown)
上创建复合索引。该索引将使用 brand = constant AND shown=1
以及 brand=constant
.
ALTER TABLE products ADD INDEX brand_shown (brand, shown);
这个索引也加速了
形式的查询SELECT * FROM products WHERE brand >= 1 AND brand <=10 AND shown = 1;
或
SELECT * FROM products WHERE brand BETWEEN 1 AND 10 AND shown = 1;
做好索引需要一些知识。阅读 https://use-the-index-luke.com/ 是一种很好的学习方式。
MySQL 可能正在执行完整的 table 扫描,因为 shown
的基数(不同值的数量)非常低。所以它猜测 table 扫描比试图弄清楚如何从索引中获取数据要便宜。
如果您可以将查询修改为
SELECT name FROM products WHERE brand = constant AND shown = 1
然后将 name
添加到索引中,您将得到 a so-called covering index。这将允许 MySQL 仅通过对索引进行范围扫描来满足您的查询,并且速度会非常快。
ALTER TABLE products ADD INDEX brand_shown_name (brand, shown, name);
专业提示 避免在性能有问题的查询中使用 SELECT *
。相反,列出您需要的列。当您这样做时,MySQL 可能会应用一些优化(例如覆盖索引优化)。
专业提示 2 避免为了良好的衡量而创建索引。创建您实际需要的查询(或强制唯一性)。额外的索引占用 space 并减慢 INSERT 和 UPDATE 操作。