ran*_*101 4 mysql sql collation query-optimization query-planner
我有一个 SQL 查询,它在表中查找特定值,然后在三个表之间进行内部联接以获取结果集。这三个表是fabric_barcode_oc
, fabric_barcode_items
&fabric_barcode_rolls
初始查询
查询的初始版本如下
EXPLAIN ANALYZE
SELECT `oc`.`oc_number` AS `ocNumber` , `roll`.`po_number` AS `poNumber` ,
`item`.`item_code` AS `itemCode` , `roll`.`roll_length` AS `rollLength` ,
`roll`.`roll_utilized` AS `rollUtilized`
FROM `fabric_barcode_rolls` AS `roll`
INNER JOIN `fabric_barcode_oc` AS `oc` ON `oc`.`oc_unique_id` = `roll`.`oc_unique_id`
INNER JOIN `fabric_barcode_items` AS `item` ON `item`.`item_unique_id` = `roll`.`item_unique_id_fk`
WHERE BINARY `roll`.`roll_number` = 'dZkzHJ_je8'
Run Code Online (Sandbox Code Playgroud)
EXPLAIN ANALYZE
在此运行时,我得到以下信息
"-> Nested loop inner join (cost=468160.85 rows=582047) (actual time=0.063..254.186 rows=1 loops=1)
-> Nested loop inner join (cost=264444.40 rows=582047) (actual time=0.057..254.179 rows=1 loops=1)
-> Filter: (cast(roll.roll_number as char charset binary) = 'dZkzHJ_je8') (cost=60727.95 rows=582047) (actual time=0.047..254.169 rows=1 loops=1)
-> Table scan on roll (cost=60727.95 rows=582047) (actual time=0.042..198.634 rows=599578 loops=1)
-> Single-row index lookup on oc using PRIMARY (oc_unique_id=roll.oc_unique_id) (cost=0.25 rows=1) (actual time=0.009..0.009 rows=1 loops=1)
-> Single-row index lookup on item using PRIMARY (item_unique_id=roll.item_unique_id_fk) (cost=0.25 rows=1) (actual time=0.006..0.006 rows=1 loops=1)
"
Run Code Online (Sandbox Code Playgroud)
更新的查询
然后我将查询更改为
EXPLAIN ANALYZE
SELECT `oc`.`oc_number` AS `ocNumber` , `roll`.`po_number` AS `poNumber` ,
`item`.`item_code` AS `itemCode` , `roll`.`roll_length` AS `rollLength` ,
`roll`.`roll_utilized` AS `rollUtilized`
FROM `fabric_barcode_rolls` AS `roll`
INNER JOIN `fabric_barcode_oc` AS `oc` ON `oc`.`oc_unique_id` = `roll`.`oc_unique_id`
INNER JOIN `fabric_barcode_items` AS `item` ON `item`.`item_unique_id` = `roll`.`item_unique_id_fk`
WHERE `roll`.`roll_number` = 'dZkzHJ_je8'
Run Code Online (Sandbox Code Playgroud)
这会生成以下执行计划
"-> Rows fetched before execution (cost=0.00 rows=1) (actual time=0.000..0.000 rows=1 loops=1)
Run Code Online (Sandbox Code Playgroud)
这两个查询之间的唯一区别是我BINARY
从查询中删除了函数调用。我很困惑为什么计划如此不同?
执行时间
查询 1 的执行时间约为 375 毫秒,而第二个查询的执行时间约为 160 毫秒。
是什么造成了这种差异?
更新
fabric_barcode_rolls
根据要求包括表架构定义
fabric_barcode_rolls,"CREATE TABLE `fabric_barcode_rolls` (
`roll_unique_id` int NOT NULL AUTO_INCREMENT,
`oc_unique_id` int NOT NULL,
`item_unique_id_fk` int NOT NULL,
`roll_number` char(30) NOT NULL,
`roll_length` decimal(10,2) DEFAULT '0.00',
`po_number` char(22) DEFAULT NULL,
`roll_utilized` decimal(10,2) DEFAULT '0.00',
`user` char(30) NOT NULL,
`mir_number` char(22) DEFAULT NULL,
`mir_location` char(10) DEFAULT NULL,
`mir_stamp` datetime DEFAULT NULL,
`creation_stamp` datetime DEFAULT CURRENT_TIMESTAMP,
`update_stamp` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`roll_unique_id`),
UNIQUE KEY `roll_number` (`roll_number`),
KEY `fabric_barcode_item_fk` (`item_unique_id_fk`),
CONSTRAINT `fabric_barcode_item_fk` FOREIGN KEY (`item_unique_id_fk`) REFERENCES `fabric_barcode_items` (`item_unique_id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=610684 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci"
Run Code Online (Sandbox Code Playgroud)
您的性能差异是由于以下事实造成的:在 MySQL 中,VARCHAR() 和 CHAR() 列上的排序规则被烘焙到索引中。
编辑更新以匹配表定义。
您的fabric_barcode_rolls
表有一列定义如下:
roll_number char(30) NOT NULL,
...
UNIQUE KEY roll_number (roll_number).
Run Code Online (Sandbox Code Playgroud)
因此,您的WHERE ... BINARY roll.roll_number = 'dZkzHJ_je8'
过滤子句不可控制:它不能使用该列上的索引。但它WHERE ... roll.roll_number = 'dZkzHJ_je8'
是可控制的:它确实使用索引。所以速度很快。但该列的默认排序规则不区分大小写。所以,它很快而且是错误的。
这是可以解决的。
请注意,该列上没有排序规则声明。这意味着它使用表的默认值:utf8mb4_0900_ai_ci
,不区分大小写的排序规则。
对于普通条形码列,您需要的是每个字符一个字节的字符集和区分大小写的排序规则。这会改变你的桌子来做到这一点。
ALTER TABLE fabric_barcode_rolls
CHANGE roll_number
roll_number CHAR(30) COLLATE latin1_bin NOT NULL;
Run Code Online (Sandbox Code Playgroud)
这是多层次的胜利。使用正确的条形码字符集可以节省数据。它使索引更短并且使用更高效。它执行区分大小写(二进制匹配)的查找,这本身使索引更短且使用效率更高。而且它不会存在大小写字符集的条形码之间的冲突风险。
在您得出碰撞风险如此之低以至于不必担心之前,请阅读生日悖论。