因为一条SQL,我差点被祭天......( 二 )


而如果要查找“张三 , 10”这一条特定的数据 , 就可以用 name = ''张三'' and age = 10 获取 。
因为联合索引的键值对是两个 , 所以只要前面的 name 确定的情况下就可以进一步定位到具体的 age 记录 。
但是如果你的查询条件只有 age 的话 , 那么索引就不会生效 , 因为没有匹配最左边的字段 , 后面所有的索引字段都不会生效 。
所以我之前写的 SQL 语句才会因为少了最左边的 user_fruit_id 字段而走了全表扫描的查询方式 。
正常来说 , 假设一个联合索引设计成(a , b)这样的结构的话 , 那么用 a and b 作为条件 , 或者 a 单独作为查询条件都会走索引 , 这种情况下我们就不要再为 a 字段单独设计索引了 。
但如果查询条件里面只有 b 的语句 , 是无法使用(a , b)这个联合索引的 , 这时候你不得不维护另外一个索引 , 也就是说你需要同时维护(a , b)、(b) 这两个索引 。
找出 Bug
虽然临时做了处理 , 但问题并不算解决 , 很明显是系统出现了 Bug 才会有走这样的查询条件 。
因为是我自己写的代码 , 所以知道是哪条 SQL 后我就马上定位到了代码里的具体方法 , 后来才发现是因为我对 user_fruit_id 字段的判空处理不生效所致 。
因为该字段是从调用方传过来的 , 所以我在方法参数里对该字段做了非空限制的注解 , 也就是 javax 包下的 @NotNull:
public class GardenUserTaskListReq implements Serializable { private static final long serialVersionUID = -9161295541482297498L; @ApiModelProperty(notes = ''水果id'') @NotNull(message = ''水果id不能为空'') private Long userFruitId; /**以下省略*/ ..................... }虽然加上该注解来做非空校验 , 但我却没有在参数加上另一个注解 @Validated 。
该注解如果没加上的话 , 那么调用 javax 包下的校验规则就都不生效 , 正确的写法是在 controller 层方法的参数前面加上注解:
因为一条SQL,我差点被祭天......
本文插图
除此之外 , 因为 user_fruit_id 这个字段是另一张表的主键 , 我在代码里也没有对这张表是否存在这个 id 做查询判断 。
这样一来 , 无论调用方传什么值过来都会直接触发 SQL 查询 , 并且在不跑索引的情况下直接走全表扫描 。
因为一条SQL,我差点被祭天......
本文插图
不得不说 , 这真是个低级错误 , 说真的 , 我对这个原因真是感到嘀笑皆非 , 再怎么说也工作几年了 , 怎么还犯一些新手级别的错误呢 , 这脸打得真是让我相当惭愧 。
总结
虽然是低级错误 , 但造成的后果也算挺严重了 , 这次事件也让我更加的警醒 , 在以后的开发工作中必须要遵守该有的原则 , 大概有这么几点:
①不能相信调用端 。 重要的参数都要先做验证 , 即使是非空值也需要做验证 , 不符合条件的就要直接返回或抛异常 , 不能参与业务 SQL 的查询 , 否则频繁的访问也会对服务造成负担 。
②SQL 语句要先做性能查询 。 对于数据量大的表 , 建好索引后 , 所有的 SQL 查询语句要用 explain 检测性能 , 并且根据结果来进一步优化索引 。
③代码必须要 Review 。 之前我没有放太大的精力在代码的 Review 上 , 虽说跟迭代排期的紧凑也有关系 , 但不管怎么说 , Bug 确实是我的疏忽造成的 , 尤其是像空值这种细小的错误在 Java 里可以说家常便饭 。
千里之堤毁于蚁穴 , 有时一个小 Bug 很容易就引发整个系统的崩盘 , 这一次的问题也让我更加深刻的认识到了 Review 代码的重要性 , 不管业务开发的工作量有多麻烦 , 这一步操作绝对不能忽视 。
作者:鄙人薛某