上周同事有條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修復。
總結
想用人眼兜底所有的風險終究是不靠譜的。像這種有風險的操作應該走平臺,讓平臺承擔備份和提醒的工作~