mysql表关联的索引命中失败 range checked for each record

mysql表关联的索引命中失败和由此带来的思考

###问题描述
最近翻了慢查询日志,大多数都是备份,夜晚的临时查询表生成,但是偶尔有几句是早年间留下的legend code留下的错误.有几个显示的问题
目前看来对于关联大表都要小心谨慎,尤其是关联条件的使用,本次出现的问题就在于join的on条件下过长导致的
虽然使用慢查询在生产环境中可以看到
LEFT JOIN orders o ON u.user_id = o.user_id and o.chain_id = ‘158’ and o.create_date > ‘2022-01-01’ and o.create_date < ‘2022-02-01’

# Query_time: 12.742334 Lock_time: 0.000586 Rows_sent: 4 >\Rows_examined: 17050666
SET timestamp=1642946504;

LEFT JOIN orders o ON u.user_id = o.user_id and o.chain_id = ‘158’ and o.create_date > ‘2022-01-01’ and o.create_date < ‘2022-02-01’

###问题分析
使用虽然explain可以看到索引没有命中,普通的索引都没有命中,给其他地方优化的联合索引也没命中(这和联合索引的左命中原理有关)

在这里插入图片描述

主要的原因在于orders表是大表,在关联后的命中索引失败后,他的产生的性能损失就很大.所以要想办法用其他方式命中索引.这里面涉及到很底层的mysql优化问题,我们普通的开发者是很难花时间去学习的.在这里我首先是查找了google,查了range checked for each record
,然后一个stack讨论很有意思(搜索引擎的使用是一个人的重要技能,马前卒有一句话很有意思,大多数人而言他们百度都没有使用的透彻)
https://stackoverflow.com/questions/54459775/mysql-join-between-range-checked-for-each-record-index-map-0x7

最后给的优化思路是吧on里面根据条件和订单表的索引的其他两个条件拆分出来,这样表关联中命中了user_id和chain_id
而 create_data放到了where语句中.虽然如此还要额外在写一句查询来匹配空出的数据.但是时间也拉到了正常的水平内.

思考

在mysql的慢查询中,出现过的问题要小心大表的,尤其是出现表关联后,虽然我们知道不同版本下的mysql优化也在不断地进步,很多早年间写的慢语句,建议都已经不在符合新版本.但是有几个核心的建议还是始终符合的.

  • 大表的关联和查询都要小心,是否命中索引都要小心,一旦索引命中失败,带来的成本将是恐怖的.使用子查询提前命中索引后查出条目放入临时表.使用缓存把之前语句提前提取出来,使用多个短查询代替表关联.都是很好用的做法
  • 对数量级要有敏感度,对于几千条表下,ALL(没命中索引)其实也是无所谓的.而达到几十万后,索引命中失败带来的压力就不容忽视.类似的类似bool类型索引实际上对于大表下他的优势就没有那么大了,即便命中了,带来的提升也没有那么大.再比如使用表关联后,一旦索引命中失败,那么即便是两个中等大小的表,他的乘积也会变成大表,这时候就要小心优化了.
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
最新最完整的中英法文ISO3166世界行政区划MySQL级联,包含中国 -- -- 的结构 `API_location_country` -- CREATE TABLE `API_location_country` ( `id` int(11) NOT NULL, `name_en` varchar(255) DEFAULT NULL COMMENT '英文名', `name_zh-Hans` varchar(255) DEFAULT NULL COMMENT '简体中文名', `name_fr` varchar(255) NOT NULL COMMENT ‘法文名', `a2code` varchar(2) NOT NULL, `a3code` varchar(3) NOT NULL COMMENT 'alpha3 code', `numberic` int(3) NOT NULL, `last_modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='国家'; -- -- 转存中的数据 `API_location_country` -- INSERT INTO `API_location_country` (`id`, `name_en`, `name_zh-Hans`, `name_fr`, `a2code`, `a3code`, `numberic`, `last_modified`) VALUES (1, 'Afghanistan', '阿富汗', 'Afghanistan (l\')', 'AF', 'AFG', 4, '2017-11-06 16:48:14'); -- -- -- 的结构 `API_location_state` -- CREATE TABLE `API_location_state` ( `id` int(11) NOT NULL, `name_en` varchar(50) DEFAULT NULL COMMENT '英文名', `name_zh-Hans` varchar(255) DEFAULT NULL COMMENT '简体中文名', `code` varchar(50) DEFAULT NULL COMMENT '代号', `countryId` int(11) DEFAULT NULL COMMENT '所属国家', `checked` int(1) DEFAULT '0' COMMENT '是否有子类0示有1示无' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='二级行政区'; -- -- 转存中的数据 `API_location_state` -- INSERT INTO `API_location_state` (`id`, `name_en`, `name_zh-Hans`, `code`, `countryId`, `checked`) VALUES (1, 'Herat', '赫拉特', 'HEA', 1, 1), (2, 'Kabul', '喀布尔', 'KBL', 1, 1), (3, 'Kandahar', '坎大哈', 'KDH', 1, 1), (4, 'Mazar-i Sharif', '马扎里沙里夫', 'MZR', 1, 1), (5, 'Berat', '培拉特', 'BR', 3, 1), (6, 'Diber', '迪勃拉', 'DI', 3, 1), (7, 'Durres', '都拉斯', 'DR', 3, 1), (8, 'Elbasan', '爱尔巴桑', 'EL', 3, 1), (9, 'Fier', '费里', 'FR', 3, 1), (10, 'Gjirokaster', '吉诺卡斯特', 'GJ', 3, 1);

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值