业务场景介绍
在电商项目中,有一个商品表【t_goods】和一个商品sku表【t_goods_sku】,具体表结构如下所示:
CREATE TABLE `t_goods` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id',
`brand_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '品牌ID',
`buy_count` int NULL DEFAULT 0 COMMENT '购买数量',
`comment_num` int NULL DEFAULT NULL COMMENT '评论数量',
`cost` decimal(16, 4) NULL DEFAULT NULL COMMENT '成本价格',
`goods_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商品名称',
`goods_unit` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '计量单位',
`grade` decimal(16, 4) NULL DEFAULT NULL COMMENT '商品好评率',
`intro` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '商品详情',
...
PRIMARY KEY (`id`) USING BTREE,
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '商品表' ROW_FORMAT = Dynamic;
CREATE TABLE `t_goods_sku` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id',
`goods_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商品ID',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商品名称',
`unit` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '计量单位',
`intro` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '商品详情',
`market_enable` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '上架状态',
`auth_flag` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '审核状态',
`price` decimal(16, 4) NULL DEFAULT NULL COMMENT '商品价格',
`quantity` int NULL DEFAULT NULL COMMENT '库存',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_goods_id_market_enable_auth_flag`(`goods_id` ASC, `market_enable` ASC, `auth_flag` ASC) USING BTREE COMMENT '商品id、上架状态、审核状态联合索引',
INDEX `inx_goods_id`(`goods_id` ASC) USING BTREE COMMENT '商品id'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '商品sku表' ROW_FORMAT = Dynamic;
在t_goods_sku表中通过goods_id可以找到指定商品的所有sku信息,这正是出现索引失效的场景。
索引失效问题描述
根据goods_id获取该商品对应的sku列表信息,代码如下:
goodsSkuService.list(new LambdaQueryWrapper<GoodsSku>().eq(GoodsSku::getGoodsId, goodsId))
这段代码在实际执行中发现比较慢,通过explain命令查看执行计划:
EXPLAIN
SELECT * FROM qu_goods_sku WHERE goods_id = 99994373;
执行结果如下:
从执行结果来看,t_goods_sku两个索引,一个都未命中。
索引失效问题分析
MySQL 索引失效是一个常见的性能问题,它可能导致查询变慢甚至全表扫描。以下是一些常见的导致索引失效的情况和解决方法的总结:
-
不使用索引列进行查询:当查询条件中不包含索引列,MySQL 将无法使用索引来加速查询。解决方法是确保查询条件中包含适当的索引列,以便 MySQL 可以使用索引来执行查询。
-
索引列上使用函数或表达式:如果在索引列上使用函数或表达式,MySQL 将无法使用索引。解决方法是尽量避免在索引列上使用函数或表达式,或者考虑创建函数索引来支持这些查询。
-
列类型不匹配:当查询条件中的列类型与索引列的类型不匹配时,MySQL 可能无法使用索引。解决方法是确保查询条件的列类型与索引列的类型相匹配。
-
组合索引顺序不正确:对于组合索引,索引列的顺序非常重要。如果查询条件中的列顺序与组合索引的列顺序不匹配,MySQL 可能无法使用索引。解决方法是确保查询条件中的列顺序与组合索引的列顺序一致。
-
数据分布不均匀:如果索引列的数据分布不均匀,MySQL 可能无法有效地使用索引。这种情况下,可以考虑重新设计索引或使用分区表来改善数据分布。
-
过多的索引:过多的索引可能导致索引失效和性能下降。解决方法是仅创建必要的索引,并定期评估和优化现有索引。
-
隐式类型转换:当查询条件中的列类型与索引列的类型不匹配时,MySQL 可能会进行隐式类型转换,导致索引失效。解决方法是确保查询条件中的列类型与索引列的类型完全匹配。
-
字符串列使用前缀索引:如果字符串列使用了前缀索引,MySQL 可能无法使用索引。解决方法是考虑增加索引长度或使用全文索引来支持模糊查询。
-
高基数列索引失效:当索引列的基数(不同值的数量)非常高时,MySQL 可能会选择不使用索引。解决方法是评估索引列的基数和查询的选择性,并根据情况调整索引策略。
-
更新频繁的表索引失效:当表上的索引需要频繁更新时,索引可能会失效。解决方法是根据具体情况权衡索引的更新成本和查询性能,并选择合适的索引策略。
针对笔者项目中碰到的问题,通过分析发现,是由于第3点【列类型不匹配】导致的。
GoodsSku对象的goodsId定义为String类型,但是在使用MybatisPlus进行代码查询对象时LambdaQueryWrapper构建时,可以接受Long型值,所以导致sql中where语句为goods_id = 99994373,然后,goods_sku表中goods_id字段是varchar类型,由于传入的列值与列类型不匹配,导致索引失效。
如何解决呢?只要在代码中将传入的Long类型值转为String即可,代码如下:
goodsSkuService.list(new LambdaQueryWrapper<GoodsSku>().eq(GoodsSku::getGoodsId, String.valueOf(goodsId)))
这样子,执行sql如下:
SELECT * FROM qu_goods_sku WHERE goods_id = '99994373';
使用explain命令查看执行计划:
从上图可以看出,修改参数值类型之后,命中了索引idx_goods_id_market_enable_auth_flag,实际执行效率也得到了明显提升。