MySQL UPDATE順序導致主從同步異常問題一例

前些天公司論壇的一個從數據庫報了一次Duplicate entry(1062)錯誤,現在的論壇都搭建了雙從庫,而登錄另一臺從庫看到是同步正常的,這就有些詭異了。起初以為是mysql版本問題導致,但是檢查后發現同步正常的那臺機器的mysql版本跟主庫不一致,而恰恰是出問題的機器與主庫的版本是一致的。出錯的語句是一個合并帖子的UPDATE操作,由此引出了這個問題,問題最初是順安發現的。

1 問題描述

論壇從Discuz 7.2升級到X3.2,并基于Discuz X3.2版本做了許多的定制和插件開發,用到的MySQL版本為5.1,存儲引擎為MyISAM,binlog_format為STATEMENT。這個問題簡單描述就是:論壇里面有個帖子表,在合并帖子的時候會對帖子的位置信息進行更新,在執行位置更新語句的時候主庫和另一個從庫沒有報錯,而其中一個從庫報錯了。為了方便描述,這里先創建一個測試表post,

CREATE TABLE `post` (
  `i` int(11) DEFAULT NULL,
  `p` int(11) NOT NULL,
  PRIMARY KEY (`p`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1

然后在里面插入幾條數據 insert into post values (1,1), (2,3), (3,4)。在論壇里面合并帖子的時候會執行一個帖子位置更新的操作 update post set p=p+1;,這樣我們看到這個操作就報錯了。

mysql> insert into post values(1,1), (2,3),(3,4);
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> update post set p=p+1;
ERROR 1062 (23000): Duplicate entry '4' for key 'PRIMARY'

mysql> select * from post;
+------+---+
| i    | p |
+------+---+
|    1 | 2 |
|    2 | 3 |
|    3 | 4 |
+------+---+
3 rows in set (0.00 sec)

2 解決方案

看到這里相比大家都發現了,這是因為UPDATE的順序問題導致出現了鍵值沖突,因為我們并沒有指定UPDATE的順序,而恰好我們看到的順序是按照p=1, 3, 4的順序來執行,因此,第一條記錄(1,1)成功更新成了(1,2),而執行到p=3的時候,因為p+1=4與已有的值4沖突報錯。要解決這個問題的方式也很簡單,在更新位置信息時指定順序為DESC,這樣從大到小執行更新就不會出錯了,update post set p=p+1 order by p desc(ps:之前很少在UPDATE語句中用ORDER BY 語句,還以為不支持,查了下手冊確認了下是OK的)。MySQL手冊里面還特意提到過這個問題,可能Discuz開發人員沒有注意到這一頁,當然Discuz里面數據庫操作有許多有性能問題的SQL語句,我們已經修過很多BUG了。

If an UPDATE statement includes an ORDER BY clause, the rows are updated in the 
order specified by the clause. This can be useful in certain situations that might 
otherwise result in an error. Suppose that a table t contains a column id that has 
a unique index. The following statement could fail with a duplicate-key error, 
depending on the order in which rows are updated:

UPDATE t SET id = id + 1;

For example, if the table contains 1 and 2 in the id column and 1 is updated to 2 
before 2 is updated to 3, an error occurs. To avoid this problem, add an ORDER BY 
clause to cause the rows with larger id values to be updated before those with 
smaller values:

UPDATE t SET id = id + 1 ORDER BY id DESC;

3 幾個問題

  • 1)MySQL的SELECT和UPDATE語句的默認順序是怎么樣的?

    這個問題mysql手冊中也有提到過,SELECT/UPDATE語句的默認順序跟數據文件以及存儲引擎等相關,在寫SQL語句的時候千萬不要依賴默認順序,如有需要,請加上ORDER BY.

  • 2)為什么版本一致,但是主庫沒有報錯而從庫報錯?

    這就是第一個問題里面說的,雖然主庫和從庫版本相同,但是由于數據組織不同導致主庫UPDATE的順序跟從庫不同,巧合的避過了重復鍵的錯誤。如果本身主庫和從庫的UPDATE順序一致,那么主庫本身就會只執行部分更新并報錯,從之前的那篇 MySQL binlog格式解析可以知道,這種情況下binlog里面是會記錄一個錯誤碼的,那么從庫在執行的時候雖然也會有一個 Duplicate entry(1062)錯誤,但是對比binlog的錯誤碼是一致的,所以同步并不會出錯。

參考資料

http://dev.mysql.com/doc/refman/5.7/en/update.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容