上周同事有條update SQL沒有加條件就執(zhí)行了敷钾,在DBA大佬的及時(shí)搶救下沒有釀成事故。那條SQL比較有趣乙漓,簡單分析一下熔掺。
分析過程
原表的結(jié)構(gòu):
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 | |
+---------+------------------+------+-----+---------+----------------+
表中的數(shù)據(jù):
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 |
+----+--------+---------+---------+
執(zhí)行的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
更新的結(jié)果:
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語句如果需要更新多個(gè)字段,被更新的值需要用逗號(hào)分隔旭愧,而不是and颅筋。從更新結(jié)果看到宙暇,status字段全表被更新為1或者0,推斷MySQL解析器把 and 連接的條件做了 與或運(yùn)算 從而得到了bool值(true為1议泵, false為0)占贫。用sqlparser進(jìn)行試驗(yàn),結(jié)果成立肢簿。
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))
}
從結(jié)果中可以看到靶剑,status被設(shè)置為expr里面的值蜻拨。
field: status
expr : 10 and rule_id != 100 and role_id in (2, 3)
update語句中含有in條件池充,猜想 in 被解析成或運(yùn)算執(zhí)行的,觀察這條被更新為1的結(jié)果和其原來的數(shù)據(jù)可以得出結(jié)論缎讼。
select * from update_test;
+----+--------+---------+---------+
| id | status | user_id | rule_id |
+----+--------+---------+---------+
| 1 | 2 | 10001 | 1 | --- 原數(shù)據(jù) 更新條件為(status=2 && rule_id!= 100 && user_id=10001) 此記錄均滿足收夸,猜想成立
+----+--------+---------+---------+
mysql> select * from update_test;
+----+--------+---------+---------+
| id | status | user_id | rule_id |
+----+--------+---------+---------+
| 1 | 1 | 10001 | 1 | --- update之后的數(shù)據(jù)
+----+--------+---------+---------+
有的同學(xué)可能要說了,MySQL是有sql_safe_updates
配置的血崭,默認(rèn)關(guān)閉卧惜,只要打開,那么不加條件的update語句就無法執(zhí)行夹纫,就不會(huì)出現(xiàn)這樣的問題了咽瓷,一勞永逸!
show variables like "sql_safe_updates"; -- 查看變量
set sql_safe_updates = 1; -- session級(jí)別打開
這樣其實(shí)是不行的舰讹,因?yàn)闃I(yè)務(wù)千奇百怪茅姜,有的場(chǎng)景需要不帶條件的update, 而且如果開了,估計(jì)有的ORM就直接用不了了吧月匣,到時(shí)候開發(fā)就該吐槽DBA了...
這是本人的想法钻洒,筆者又去問了一位資深數(shù)據(jù)庫從業(yè)人員,那位大佬說的話非常有哲理锄开,瞬間上升了一個(gè)維度:技術(shù)是用來保障服務(wù)的素标,而不是限制用戶的,如果出現(xiàn)了全表更新萍悴,用flashback修復(fù)头遭。
總結(jié)
想用人眼兜底所有的風(fēng)險(xiǎn)終究是不靠譜的。像這種有風(fēng)險(xiǎn)的操作應(yīng)該走平臺(tái)癣诱,讓平臺(tái)承擔(dān)備份和提醒的工作~