Part1:寫在最前
上班正忙的不可開交呢,一個消息過來,得知研發人員誤操作數據庫了....不帶where條件,整表更新Orz,還讓不讓人好好活了,心中一萬隻XXX啊~無奈,分清事情的輕重,優先處理這起事故。
在簡單溝通後,瞭解到事故的原因是研發人員使用update忘記帶where條件。這本身沒什麼詭異的,詭異的是在決定要不要進行恢復的時候,筆者稍微猶豫了一下,因爲看起來是不需要恢復的,那麼具體是什麼樣的情況呢?
Part2:危險場景再現
研發人員update使用了錯誤的語法,本意是update helei3 set a='1' where b='a';
結果寫成了update helei3 set a='1' and b='a';
這樣的話使得helei3這張表的a列被批量修改爲0或1。
過了幾秒鐘,發現寫錯並且已經敲了回車後,此時update語句還沒有更新完,立即ctrl+c
那麼數據到底有沒有被寫髒?
復現
Part1:創建所需表
首先我們創建測試表,a列b列均爲varchar類型
[email protected] (helei)> show create table helei3\G
*************************** 1. row ***************************
Table: helei3
Create Table: CREATE TABLE `helei3` (
`a` varchar(10) DEFAULT NULL,
`b` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
表中數據如下
[email protected] (helei)> select * from helei3;
+------+------+
| a | b |
+------+------+
| 1 | a |
| 2 | b |
| 3 | c |
+------+------+
3 rows in set (0.00 sec)
Part2:錯誤語句生成
我們都知道,update的語法是update tablename set col1=val,col2=val2 where xxx;
那麼當逗號換成了and,會出現什麼樣的嚴重後果呢?
這個時候由於沒有where條件,導致整表更新,那猜猜看後續結果是什麼
[email protected] (helei)> update helei3 set a='1' and b='a';
[email protected] (helei)> select * from helei3;
+------+------+
| a | b |
+------+------+
| 1 | a |
| 0 | b |
| 0 | c |
+------+------+
4 rows in set (0.00 sec)
沒錯,這個SQL將a列整表更新爲0,而之所以第一個a=1是由於a='1' and b='a'這個條件是真,所以爲1。
Part3:ctrl+c
瞭解Part2後,我們再看下當update全表更新發現誤操作後立即ctrl+c能不能回滾避免誤操作。
提前準備好一張50萬數據的表
[email protected] (helei)> select count(*) from helei;
+----------+
| count(*) |
+----------+
| 500000 |
+----------+
1 row in set (0.06 sec)
[email protected] (helei)> show create table helei\G
*************************** 1. row ***************************
Table: helei
Create Table: CREATE TABLE `helei` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`c1` int(10) NOT NULL DEFAULT '0',
`c2` int(10) unsigned DEFAULT NULL,
`c5` int(10) unsigned NOT NULL DEFAULT '0',
`c3` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`c4` varchar(200) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `idx_c1` (`c1`),
KEY `idx_c2` (`c2`)
) ENGINE=InnoDB AUTO_INCREMENT=500001 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)
誤操作整表更新後等待幾秒立即ctrl + c
[email protected] (helei)> update helei set c2=1;
^CCtrl-C -- sending "KILL QUERY 2133" to server ...
Ctrl-C -- query aborted.
^CCtrl-C -- sending "KILL 2133" to server ...
Ctrl-C -- query aborted.
ERROR 2013 (HY000): Lost connection to MySQL server during query
[email protected] (helei)> select * from helei where c2=1;
Empty set (0.00 sec)
可以看到c2列並沒有出現部分更新爲1的情況,也就是說整表更新的這條操作回滾了。
細心點可以看到binlog pos號也沒有發生變化
[email protected] (helei)> show master status;
+------------------+-----------+--------------+------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+-----------+--------------+------------------+
| mysql-bin.000004 | 124886658 | | |
+------------------+-----------+--------------+------------------+
1 row in set (0.00 sec)
Part4:詭異
前三章看完後,我們來看下有什麼地方是詭異的,在生產環境中,由於不知道剛剛那條SQL是否已經更新了部分數據,我們採取了這種方式來驗證。
[email protected] (helei)> select * from helei3 where a='0';
+------+------+
| a | b |
+------+------+
| 0 | b |
| 0 | c |
+------+------+
2 rows in set (0.00 sec)
[email protected] (helei)> select * from helei3 where a=0;
+------+------+
| a | b |
+------+------+
| 0 | b |
| 0 | c |
| zz | zz |
+------+------+
3 rows in set (0.00 sec)
發現數據不一致,生產環境的更唬人一些,列中並沒有存儲0,而都是字母或純數字,當我執行上述兩個SQL的時候,發現結果差了非常多,還爆出了很多的warnings。
| Warning | 1292 | Truncated incorrect DOUBLE value: 'XXX' |
那麼我想知道剛剛的誤操作到底是不是生效了呢,爲什麼會出現差個引號結果就差這麼多呢?
分析
Part1:構建數據
[email protected] (helei)> insert into helei3 values('zz','zz');
[email protected] (helei)> select * from helei3;
+------+------+
| a | b |
+------+------+
| 1 | a |
| 0 | b |
| 0 | c |
| zz | zz |
+------+------+
4 rows in set (0.00 sec)
Part2:查詢對比
那麼這時我們執行一條查詢會有兩種結果
[email protected] (helei)> select * from helei3 where a='0';
+------+------+
| a | b |
+------+------+
| 0 | b |
| 0 | c |
+------+------+
2 rows in set (0.00 sec)
[email protected] (helei)> select * from helei3 where a=0;
+------+------+
| a | b |
+------+------+
| 0 | b |
| 0 | c |
| zz | zz |
+------+------+
3 rows in set (0.00 sec)
這是爲什麼呢?
Part3:root cause
[email protected] (helei)> select 'zz'=0;
+--------+
| 'zz'=0 |
+--------+
| 1 |
+--------+
1 row in set, 1 warning (0.00 sec)
[email protected] (helei)> select 'zz3'=0;
+---------+
| 'zz3'=0 |
+---------+
| 1 |
+---------+
1 row in set, 1 warning (0.00 sec)
[email protected] (helei)> select '3'=0;
+-------+
| '3'=0 |
+-------+
| 0 |
+-------+
1 row in set (0.00 sec)
可以看出,當包含字母的時候,mysql認爲=0是真,並拋出warning。
[email protected] (helei)> show warnings;
+---------+------+----------------------------------------+
| Level | Code | Message |
+---------+------+----------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: 'zz' |
+---------+------+----------------------------------------+
1 row in set (0.00 sec)
Part4:MySQL Doc
In InnoDB
, all user activity occurs inside a transaction. If autocommit
mode is enabled, each SQL statement forms a single transaction on its own. By default, MySQL starts the session for each new connection with autocommit
enabled, so MySQL does a commit after each SQL statement if that statement did not return an error. If a statement returns an error, the commit or rollback behavior depends on the error.
Part5:我的理解
InnoDB存儲引擎符合事務的ACID特性。 它將一次完成所有操作,或者在中斷時不會執行操作和回滾。 InnoDB也是MySQL 5.5及以上版本的默認引擎。
但是對於非事務性的MyISAM存儲引擎。 他的原子操作是一行一行完成的。 所以如果你中斷這個過程,那就會更新/刪除到哪裏就到哪裏了。
++++++++++++++
1.這個其實第一個 update helei3 set a='1' and b='a'; 這個很正常,因爲這個update 其實是把a列更新爲 ‘1’ and b=‘a’ 的表達式後的值!
2. 至於說第二個大更新,中途中斷,會回滾掉,
3.第三個 加引號與不加引號的區別,其實就是mysql 隱身轉換問題,如果字段類型爲varchar 或者char ,當條件爲和整形數字做比較時,會將完全爲字符的串 轉爲0 ,0=0 很正常, 如果以字母開頭比如‘12abcd’ 那麼會轉換爲12 ,其實也就是個隱式轉換問題!