1. 程式人生 > >Mysql-Proxy 讀寫分離的各種坑,特別是複製延遲時

Mysql-Proxy 讀寫分離的各種坑,特別是複製延遲時

延遲問題
讀寫分離不能迴避的問題之一就是延遲,可以考慮Google提供的SemiSyncReplicationDesign補丁。

埠問題
MySQL-Proxy預設使用的是4040埠,如果你想透明的把3306埠的請求轉發給4040的話,那麼可以:

iptables -t nat -I PREROUTING -s ! 127.0.0.1 -p tcp --dport 3306 -j REDIRECT --to-ports 4040

查詢亂碼

連線上MySQL-Proxy後,執行查詢時,隨機出現亂碼。出現此問題的原因是當我們使用MySQL-Proxy讀寫分離時,通常會有多個後端伺服器,客戶端發出查詢請求時,一般會先發出一條類似"SET NAME gbk"的語句來宣告客戶端編碼,然後再發出實際查詢的SQL語句,但MySQL-Proxy可能會把這兩條語句分發給不同的後端伺服器,於是就出現了亂碼。

解決方法是強行指定後端伺服器的字元編碼:

init-connect='SET NAME gbk'

default-character-set=gbk
skip-character-set-client-handshake

如果使用init-connect,則需要注意操作使用者不能有SUPER許可權,否則此選項無效。

即便做好了以上的設定後,還有可能會出現亂碼,比如說資料庫是gbk的,當我們用PHPMyAdmin連線MySQL-Proxy時,查詢還是會出現亂碼,不過這是正常的!因為PHPMyAdmin使用的是utf8編碼,它發出的“SET NAMES utf8”語句被skip-character-set-client-handshake遮蔽了,所以出現亂碼。

有狀態的查詢

一些有狀態的特殊的查詢可能失效,比如說:
SELECT SQL_CALC_FOUND_ROWS ..
SELECT FOUND_ROWS()

這種查詢是有狀態的,應該保證在同一個後端處理,檢視rw-splitting.lua指令碼可以看到MySQL-Proxy實際上已經對這樣的查詢進行了 判斷,但在實際應用中發現還是存在問題。估計是指令碼寫得不咋地,實際應用中,建議大家不要使用這樣的查詢,一來沒有可移植性,而來效率也不見得好。

另一個可能會產生問題的查詢是:
INSERT ... (AUTO_INCREMENT)
SELECT LAST_INSERT_ID()

當系統執行完INSERT後,再執行SELECT時,可能已經被分發到了不同的後端伺服器,如果你使用的程式語言是PHP的話,此時應該通過 mysql_insert_id()來得到最新插入的id,每次INSERT結束後,其實對應的autoincrement值就已經計算好返回給PHP 了,你無需再發出一次獨立的查詢,直接用mysql_insert_id()就可以了。不過很多PHP程式使用的都是SELECT LAST_INSERT_ID()的方式,如AdbDB,CakePHP等等,如果你正在使用它們的話需多加小心。(當使用bigint 時,mysql_insert_id()存在問題,詳情見手冊,不過對於大多數人而言,bigint基本不會遇到,所以你可以無視這個問題)

注:對於這兩個問題,官方BUG庫裡有人給出了相應的補丁。

指令碼問題

MySQL-Proxy讀寫分離的功能是通過lua指令碼(rw-splitting.lua)實現的,但是這個指令碼年久失修,問題多多,比如說使用時可能會出現:

ERROR 1105: can't change DB to on slave

出現這個問題的原因在於當客戶端發出查詢時,MySQL-Proxy會比較當前客戶端所處資料庫和伺服器所處資料庫是否一致,如果不一致則會在服務端嘗試執行一個"USE 資料庫"的操作,一個可能性是主從伺服器的資料庫結構不同,在USE一個不存在的資料庫的時候自然會出錯,還有一個原因有些查詢操作並沒有所處資料庫這個上下文,比如說SHOW DATABASES這個查詢,並不需要事先“USE 資料庫”,只要連上伺服器就可以執行,這時候如果還嘗試同步客戶端和服務端所處的資料庫,出錯就是無法避免的事了。

rw-splitting.lua恰恰沒有遮蔽後者所描述的情況,修復方法如下,在合適的位置加入程式碼,
    if cmd.type ~= proxy.COM_INIT_DB and
        c.default_db and c.default_db ~= "" and c.default_db ~= s.default_db then
        if is_debug

                print("    server default db: " .. s.default_db)                  

                print("    client default db: " .. c.default_db)

                print("    syncronizing")

        end

    proxy.queries:prepend(2, string.char(proxy.COM_INIT_DB) .. c.default_db)

    end

在lua中,~=是不等於的意思,另外,lua裡空字串""用在if裡被認為是true,所以單靠c.default_db不夠。

順手加上is_debug的判斷,不然即使不是debug狀態,伺服器的命令行裡也會偶爾冒出一些除錯資訊。