臥槽,線上資料刪錯了,差點被老闆開除

蘇三說技術發表於2021-03-29

前言

無論是開發、測試,還是DBA,都難免會涉及到資料庫的操作,比如:建立某張表,新增某個欄位、新增資料、更新資料、刪除資料、查詢資料等等。

正常情況下還好,但如果運算元據庫時出現失誤,比如:

  1. 刪除訂單資料時where條件寫錯了,導致多刪了很多使用者訂單。
  2. 更新會員有效時間時,一次性把所有會員的有效時間都更新了。
  3. 修復線上資料時,改錯了,想還原。

還有很多很多場景,我就不一一列舉了。

如果出現線上環境資料庫誤操作怎麼辦?有沒有後悔藥?

答案是有的,請各位看官仔細往下看。

1.不要用聊天工具發sql語句

通常開發人員寫好sql語句之後,習慣通過聊天工具,比如:qq、釘釘、或者騰訊通等,發給團隊老大或者DBA線上上環境執行。但由於有些聊天工具,對部分特殊字元會自動轉義,而且有些訊息由於內容太長,會被自動分成多條訊息。

這樣會導致團隊老大或者DBA複製出來的sql不一定是正確的。

他們需要手動拼接成一條完整的sql,有時甚至需要把轉義後的字元替換回以前的特殊字元,無形之中會浪費很多額外的時間。即使最終sql拼接好了,真正執行sql的人,心裡一定很虛。

所以,強烈建議你把要線上上執行的sql語句用郵件發過去,可以避免使用聊天工具的一些弊端,減少一些誤操作的機會。而且有個存檔,方便今後有問題的時候回溯原因。很多聊天工具只保留最近7天的歷史記錄,郵件會保留更久一些。

別用聊天工具發sql語句!

別用聊天工具發sql語句!

別用聊天工具發sql語句!

重要的事情說三遍,它真的能減少一些誤操作。

2.把sql語句壓縮成一行

有些時候,開發人員寫的sql語句很長,使用了各種joinunion,而且使用美化工具,將一條sql變成了多行。在複製sql的時候,自己都無法確定sql是否完整。(為了裝逼,把自己也坑了,哈哈哈)

線上環境有時候需要通過命令列連線資料庫,比如:mysql,你把sql語句複製過來後,在命令列介面執行,由於螢幕滾動太快,這時根本無法確定sql是否都執行成功。

針對這類問題,強烈建議把sql語句壓縮成一行,去掉多餘的換行符空格,可以有效的減少一些誤操作。

sql壓縮工具推薦使用:https://tool.lu/sql/

3.運算元據之前先select一下

需要特別說明的是:本文的運算元據主要指修改刪除資料。

很多時候,由於我們人為失誤,把where條件寫錯了。但沒有怎麼仔細檢查,就把sql語句直接執行了。影響範圍小還好,如果影響幾萬、幾十萬,甚至幾百萬行資料,我們可能要哭了。

針對這種情況,在運算元據之前,把sql先改成select count(*)語句,比如:

update order set status=1 where status=0;
 

改成:

select count(*) from order where status=0;
 

查一下該sql執行後影響的記錄行數,做到自己心中有數。也給自己一次測試sql是否正確,確認是否執行的機會。

4.運算元據sql加limit

即使通過上面的select語句確認了sql語句沒有問題,執行後影響的記錄行數是對的。

也建議你不要立刻執行,建議在正在執行的時候,加上limit + select出的記錄行數。例如:

update order set status=1 where status=0 limit 1000;
 

假設有一次性更新的資料太多,所有相關記錄行都會被鎖住,造成長時間的鎖等待,而造成使用者請求超時。

此外,加limit可以避免一次性操作太多資料,對伺服器的cpu造成影響。

還有一個最重要的原因:加limit後,運算元據的影響範圍是完全可控的。

5.update時更新修改人和修改時間

很多人寫update語句時,如果要修改狀態,就只更新狀態,不管其他的欄位。比如:

update order set status=1 where status=0;
 

這條sql會把status等於0的資料,全部更新成1。

後來發現業務邏輯有問題,不應該這麼更新,需要把status狀態回滾。

這時你可能會很自然想到這條sql:

update order set status=0 where status=1;
 

但仔細想想又有些不對。

這樣不是會把有部分以前status就是1的資料更新成0?

這回真的要哭了,嗚嗚嗚。

這時,送你一個好習慣:在更新資料的時候,同時更新修改人修改時間欄位。

update order set status=1,edit_date=now(),edit_user='admin' where status=0;
這樣在恢復資料時就能通過修改人修改時間欄位過濾資料了。

後面需要用到的修改時間通過這條sql語句可以輕鬆找到:

select edit_user ,edit_date from `order` order by edit_date desc limit 50;

當然,如果是高併發系統不建議這種批量更新方式,可能會鎖表一定時間,造成請求超時。

有些同學可能會問:為什麼要同時更新修改人,只更新修改時間不行嗎?

主要有如下的原因:

  1. 為了標識非正常使用者操作,方便後面統計和定位問題。
  2. 有些情況下,在執行sql語句的過程中,正常使用者產生資料的修改時間跟你的可能一模一樣,導致回滾時資料查多了。

6.多用邏輯刪除,少用物理刪除

在業務開發中,刪除資料是必不可少的一種業務場景。

有些人開發人員習慣將表設計成物理刪除,根據主鍵只用一條delete語句就能輕鬆搞定。

他們給出的理由是:節省資料庫的儲存空間

想法是好的,但是現實很殘酷。

如果有條極重要的資料刪錯了,想恢復怎麼辦?

此時只剩八個字:沒有資料,恢復不了。(PS:或許通過binlog二進位制檔案可以恢復)

如果之前設計表的時候用的邏輯刪除,上面的問題就變得好辦了。刪除資料時,只需update刪除狀態即可,例如:

update order set del_status=1,edit_date=now(),edit_user='admin' where id=123;
 

假如出現異常,要恢復資料,把該id的刪除狀態還原即可,例如:

update order set del_status=0,edit_date=now(),edit_user='admin' where id=123;
 

7.運算元據之前先做備份

如果只是修改了少量的資料,或者只執行了一兩條sql語句,通過上面的修改人修改時間欄位,在需要回滾時,能快速的定位到正確的資料。

但是如果修改的記錄行數很多,並且執行了多條sql,產生了很多修改時間。這時,你可能就要犯難了,沒法一次性找出哪些資料需要回滾。

為了解決這類問題,可以將表做備份。

可以使用如下sql備份:

create table order_bak_2021031721 like`order`;

insert into order_bak_2021031721 select * from`order`;
 

先建立一張一模一樣的表,然後把資料複製到新表中。

也可以簡化成一條sql:

create table order_bak_2021031722 select * from`order`;

建立表的同時複製資料到新表中。

此外,建議在表名中加上bak時間,一方面是為了通過表名快速識別出哪些表是備份表,另一方面是為了備份多次時好做區分。因為有時需要執行多次sql才能把資料修復好,這種情況建議把表備份多次,如果出現異常,把資料回滾到最近的一次備份,可以節省很多重複操作的時間。

恢復資料時,把sql語句改成select語句,先在備份庫找出相關資料,每條資料對應一條update語句,還原到老表中。

8.中間結果寫入臨時表

有時候,我們要先用一條sql查詢出要更新的記錄的id,然後通過這些id更新資料。

批量更新之後,發現不對,要回滾資料。但由於有些資料已更新,此時使用相同的sql相同的條件,卻查不出上次相同的id了。

這時,我們開始慌了。

針對這種情況,我們可以先將第一次查詢的id存入一張臨時表,然後通過臨時表中的id作為查詢條件更新資料。

如果要恢復資料,只用通過臨時表中的id作為查詢條件更新資料即可。

修改完,3天之後,如果沒有出現問題,就可以把臨時表刪掉了。

9.表名前面一定要帶庫名

我們在寫sql時為了方便,習慣性不帶資料庫名稱。比如:

update order set status=1,edit_date=now(),edit_user='admin' where status=0;
假如有多個資料庫中有相同的表order,表結構一模一樣,只是資料不一樣。

由於執行sql語句的人一個小失誤,進錯資料庫了。

use trade1;

然後執行了這條sql語句,結果悲劇了。

有個非常有效的預防這類問題的方法是加資料庫名

update `trade2`.`order` set status=1,edit_date=now(),edit_user='admin' where status=0;
這樣即使執行sql語句前進錯資料庫了,也沒什麼影響。

10.欄位增刪改的限制

很多時候,我們少不了對錶欄位的操作,比如:新加、修改、刪除欄位,但每種情況都不一樣。

新加的欄位一定要允許為空

新加的欄位一定要允許為空。為什麼要這樣設計呢?

正常情況下,如果程式新加了欄位,一般是先在資料庫中加欄位,然後再發程式的最新程式碼。

為什麼是這種順序?

因為如果先發程式,然後在資料庫中加欄位。在該程式剛部署成功,但資料庫新欄位還沒來得及加的這段時間內,最新程式中,所有使用了新加欄位的增刪改查sql都會報欄位不存在的異常。

好了,就按先在資料庫中加欄位,再發程式的順序。

如果資料庫中新加的欄位非空,最新的程式還沒發,線上跑的還是老程式碼,這時如果有insert操作,就會報欄位不能為空的異常。因為新加的非空欄位,老程式碼是沒法賦值的。

所以說新加的欄位一定要允許為空。

除此之外,這種設計更多的考慮是為了程式釋出失敗時的回滾操作。如果新加的欄位允許為空,則可以不用回滾資料庫,只需回滾程式碼即可,是不是很方便?

不允許刪除欄位

刪除欄位是不允許的,特別是必填欄位一定不能刪除。

為什麼這麼說?

假設開發人員已經把程式改成不使用刪除欄位了,接下來如何部署呢?

  1. 如果先把程式部署好了,還沒來得及刪除資料庫相關表欄位。當有insert請求時,由於資料庫中該欄位是必填的,會報必填欄位不能為空的異常。
  2. 如果先把資料庫中相關表欄位刪了,程式還沒來得及發。這時所有涉及該刪除欄位的增刪改查,都會報欄位不存在的異常。

所以,線上環境必填欄位一定不能刪除的。

根據實際情況修改欄位

修改欄位要分為這三種情況:

1.修改欄位名稱

修改欄位名稱也不允許,跟刪除必填欄位的問題差不多。

  1. 如果把程式部署好了,還沒來得及修改資料庫中表欄位名稱。這時所有涉及該欄位的增刪改查,都會報欄位不存在的異常。
  2. 如果先把資料庫中欄位名稱改了,程式還沒來得及發。這時所有涉及該欄位的增刪改查,同樣也會報欄位不存在的異常。

所以,線上環境欄位名稱一定不要修改。

2.修改欄位型別

修改欄位型別時一定要相容之前的資料。例如:

  1. tinyint改成int可以,但int改成tinyint要仔細衡量一下。
  2. varchar改成text可以,但text改成varchar要仔細衡量一下。

3.修改欄位長度

欄位長度建議改大,通常情況下,不建議改小。如果一定要改小,要先確認該欄位可能會出現的最大長度,避免insert操作時出現欄位太長的異常。

此外,建議改大也需要設定一個合理的長度,避免資料庫資源浪費。

總結

本文分享了10種減少資料庫誤操作的方法,並非所有場景都適合你。特別是在一些高併發,或者單表資料量非常大的場景,你需要根據實際情況酌情選擇。但我敢肯定的是讀完這篇文章,你一定會有一些收穫,因為大部分方法對你來說是適用的,可能會讓你少走很多彎路,強烈建議收藏。

最後說一句(求關注,別白嫖我)

如果這篇文章對您有所幫助,或者有所啟發的話,幫忙關注一下,您的支援是我堅持寫作最大的動力。

求一鍵三連:點贊、轉發、在看。

關注公眾號:【蘇三說技術】,在公眾號中回覆:面試、程式碼神器、開發手冊、時間管理有超讚的粉絲福利,另外回覆:加群,可以跟很多BAT大廠的前輩交流和學習。

相關文章