一条没有条件的UPDATE的分析

2019-10-30  本文已影响0人  一根薯条

上周同事有条update SQL没有加条件就执行了,在DBA大佬的及时抢救下没有酿成事故。那条SQL比较有趣,简单分析一下。

分析过程

原表的结构:

desc update_test;
+---------+------------------+------+-----+---------+----------------+
| Field   | Type             | Null | Key | Default | Extra          |
+---------+------------------+------+-----+---------+----------------+
| id      | int(11) unsigned | NO   | PRI | NULL    | auto_increment |
| status  | int(11)          | NO   |     | NULL    |                |
| user_id | bigint(20)       | NO   |     | NULL    |                |
| rule_id | tinyint(4)       | NO   |     | NULL    |                |
+---------+------------------+------+-----+---------+----------------+

表中的数据:


select * from update_test;
+----+--------+---------+---------+
| id | status | user_id | rule_id |
+----+--------+---------+---------+
|  1 |      2 |   10001 |       1 |
|  2 |      1 |   10002 |     100 |
|  3 |      3 |   10003 |     100 |
|  4 |      4 |   10004 |     100 |
|  5 |      1 |   10005 |     100 |
|  6 |      2 |   10006 |       2 |
|  7 |      3 |   10007 |     100 |
|  8 |      2 |   10008 |       1 |
|  9 |      4 |   10009 |     100 |
| 10 |      1 |   10010 |       1 |
+----+--------+---------+---------+

执行的update SQL:

update
    update_test
set
    status = 10
    and status in (2, 3)
    and rule_id != 100
    and user_id in (
        10001,
        10002,
        10003,
        10004,
        10005
);
Query OK, 10 rows affected (0.01 sec)
Rows matched: 10  Changed: 10  Warnings: 0

更新的结果:

mysql> select * from update_test;
+----+--------+---------+---------+
| id | status | user_id | rule_id |
+----+--------+---------+---------+
|  1 |      1 |   10001 |       1 |
|  2 |      0 |   10002 |     100 |
|  3 |      0 |   10003 |     100 |
|  4 |      0 |   10004 |     100 |
|  5 |      0 |   10005 |     100 |
|  6 |      0 |   10006 |       2 |
|  7 |      0 |   10007 |     100 |
|  8 |      0 |   10008 |       1 |
|  9 |      0 |   10009 |     100 |
| 10 |      0 |   10010 |       1 |
+----+--------+---------+---------+
10 rows in set (0.01 sec)

update语句如果需要更新多个字段,被更新的值需要用逗号分隔,而不是and。从更新结果看到,status字段全表被更新为1或者0,推断MySQL解析器把 and 连接的条件做了 与或运算 从而得到了bool值(true为1, false为0)。用sqlparser进行试验,结果成立。

package main

import (
    "fmt"
    "github.com/xwb1989/sqlparser"
)

func main() {
    sql := `update update_test set status = 10 and rule_id != 100 and role_id in (2,3);`
    stmt, _ := sqlparser.Parse(sql)

    //fmt.Printf("%#v\n", stmt)
    u := stmt.(*sqlparser.Update)
    fmt.Println("field: ", u.Exprs[0].Name.Name, "\nexpr :", sqlparser.String(u.Exprs[0].Expr))
}

从结果中可以看到,status被设置为expr里面的值。

field:  status 
expr : 10 and rule_id != 100 and role_id in (2, 3)

update语句中含有in条件,猜想 in 被解析成或运算执行的,观察这条被更新为1的结果和其原来的数据可以得出结论。

select * from update_test;
+----+--------+---------+---------+
| id | status | user_id | rule_id |
+----+--------+---------+---------+
|  1 |      2 |   10001 |       1 |  --- 原数据   更新条件为(status=2 && rule_id!= 100 && user_id=10001) 此记录均满足,猜想成立
+----+--------+---------+---------+
 
 
mysql> select * from update_test;
+----+--------+---------+---------+
| id | status | user_id | rule_id |
+----+--------+---------+---------+
|  1 |      1 |   10001 |       1 |  --- update之后的数据
+----+--------+---------+---------+

有的同学可能要说了,MySQL是有sql_safe_updates 配置的,默认关闭,只要打开,那么不加条件的update语句就无法执行,就不会出现这样的问题了,一劳永逸!

show variables like "sql_safe_updates";  -- 查看变量
set sql_safe_updates = 1;                -- session级别打开

这样其实是不行的,因为业务千奇百怪,有的场景需要不带条件的update, 而且如果开了,估计有的ORM就直接用不了了吧,到时候开发就该吐槽DBA了...

这是本人的想法,笔者又去问了一位资深数据库从业人员,那位大佬说的话非常有哲理,瞬间上升了一个维度:技术是用来保障服务的,而不是限制用户的,如果出现了全表更新,用flashback修复。

总结

想用人眼兜底所有的风险终究是不靠谱的。像这种有风险的操作应该走平台,让平台承担备份和提醒的工作~

上一篇下一篇

猜你喜欢

热点阅读