前言
或许大家会认为删库跑路都是运维或者DBA的事情,或许认为我没有线上数据库权限就不可能删库跑路。但是事实并非如此,建议大家仔细阅读此文章,赶紧排查下您的代码,很可能隐藏着这种删库程序。还是要呼吁大家,这个案例大家不要学习!不要学习!不要学习!重要的事情说三遍。
业务BUG现象
最近,我们收到一位研发小伙伴的反馈,他发现了一项safety级别的bug,仅仅是执行了一个简单的数据删除操作,就差点把几百万业务流转表数据全部删除了,要是被老板知道妥妥的被劝退!马上就打算跑路了,好在及时才避免严重后果的发生。究竟发生了什么?让我们在回顾一下这位小伙伴所遭遇的问题。
开发业务需求:
需求是要根据业务任务流转ID进行软删除,然后通过定时任务定期清理,于是该研发同学写了下面一个方法代码来进行此操作。以下为抽象后的代码逻辑:
//业务流转任务更新
public void updateTaskError(long taskId) throws SQLException {
String updateSql = "UPDATE business_task SET status = '0' WHERE task_id = ?";
try (PreparedStatement pstmt = connection.prepareStatement(updateSql)) {
pstmt.setInt(1, taskId);
pstmt.executeUpdate();
}
}
//定时任务定期清理
public void deleteErrorTask() throws SQLException {
String deleteSql = "DELETE FROM business_task WHERE status = 0";
try (PreparedStatement pstmt = connection.prepareStatement(deleteSql)) {
pstmt.executeUpdate();
}
}
更新逻辑本身很简单,code review没看出问题,自测由于定时任务没有触发,所以也没有发现问题。但到了测试手里,却发现数据全没了,这要是发布到了线上,后果将非常严重!
开发同学自查,到底哪条SQL导致?
开发本地开启SQL打印后,复现场景,是这样一条SQL:
DELETE FROM business_task WHERE status = 0;
该业务任务表的status一般取值为枚举类型,比如running、success等,这里的0表示任务异常,需要进行清除。看起来这条SQL语句会清理掉异常任务,没啥问题!难道是SQL发生了隐式转换导致的?
隐式转换问题,好坑呀!
MySQL官方文档翻一翻,这个SQL为何会有隐式转换呢?
# 这里是官方文档关于隐式转换浮点数比较规则
In all other cases, the arguments are compared as floating-point (double-precision) numbers. For example, a comparison of string and numeric operands takes place as a comparison of floating-point numbers.
上面的大致解释:MySQL 会尝试从字符串中解析尽可能多的有效数字,直到遇到非数字字符或者无法继续形成有效的浮点数:
1)如果字符串无法转换为有效的浮点数,MySQL将返回0
2)字符串以浮点数字开头,将截取出有效的浮点数,例如:
- ‘123.45xyz’ -> 123.45
- ‘xyz123’ ->0
在MySQL中执行手动验证转换规则:
mysql> select CAST('123.45xyz' as DECIMAL(10, 2));
+-------------------------------------+
| CAST('123.45xyz' as DECIMAL(10, 2)) |
+-------------------------------------+
| 123.45 |
+-------------------------------------+
1 row in set (0.00 sec)
mysql> select CAST('xyz123' as DECIMAL(10, 2));
+----------------------------------+
| CAST('xyz123' as DECIMAL(10, 2)) |
+----------------------------------+
| 0.00 |
+----------------------------------+
1 row in set, 1 warning (0.00 sec)
验证后确实和上面规则中的结果一致,那业务SQL是如何触发隐式转换的呢?我们进一步进行分析。
原始业务SQL如何转换?
分析代码,发现业务SQL中的status含有非数字开头的字符串,按照隐式转换会转变成0,实际内部转换的大致如下:
DELETE FROM business_task WHERE cast(‘success’ as unsigned) = 0;
那么当定时任务触发时,在MySQL5.6版本的数据库上执行该SQL,相当于全表进行了删除,至此,业务bug已定位完成,只需将0改成’0’即可解决问题。
如何利用DBdoctor快速定位隐式类型转换?
使用DBdoctor,只需两步即可完成SQL审核的隐式转换检测:
Step 1:点击【实例诊断】
登录DBdoctor点击【实例列表】,选择对应的租户项目,找到系统涉及的实例,然后点击【实例诊断】
Step 2:查看【SQL审核】,输入业务SQL,点击审核:
批量上传待审核的SQL(可从测试环境导出)或者输入指定SQL,点击审核即可。
查看结果,发现该SQL存在隐式类型转换问题,及时处理问题SQL。
总结
各位研发小伙伴们,你们是否也会经常遇到类似问题,因为执行了一条不当的SQL更新语句,就险些造成了灾难性的数据故障!为避免这些不必要问题的发生,马上部署DBdoctor,使用SQL审核功能,快速识别潜在问题,无需生产发布和变更,就能提前评估SQL语句在实际部署后可能遇到的性能问题,并推荐最佳的全局索引策略,以确保数据库操作的高效和安全。