詳解go如何使用xorm在執(zhí)行前改寫?SQL
前言
有時候你需要再 SQL 執(zhí)行之前對于 SQL 語句進行改寫,有可能是修改表名字段名,有可能只是添加注釋,這些看起來奇怪的操作其實有時候是為了幫助在數(shù)據(jù)庫之前的 proxy 來實現(xiàn)某些功能,比如最常見的分庫分表,讀寫分離,多租戶等等。
使用Hint語法
舉個具體的例子:
有些數(shù)據(jù)庫中間件支持在 SQL 語句之前添加注釋來實現(xiàn)讀寫分離
如何在讀寫模式為可讀可寫(自動讀寫分離)的集群地址中使用Hint語法。
使用限制
僅讀寫模式為可讀可寫(自動讀寫分離)的集群地址支持Hint語法,只讀模式下的集群地址和主地址均不支持Hint語法。關(guān)于集群地址的讀寫模式信息,請參見集群地址的讀寫模式。
注意事項
Hint的路由優(yōu)化級別最高,不受一致性級別和事務拆分的約束,使用前請進行評估。
使用方法
支持在SQL語句前加上/*FORCE_MASTER*/
或/*FORCE_SLAVE*/
強制指定這條SQL的路由方向。
例如select * from test
默認會路由到只讀節(jié)點,改為/*FORCE_MASTER*/ select * from test
就會路由到主節(jié)點。
支持在SQL語句前加上/*force_node='<節(jié)點ID>'*/
強制指定在某節(jié)點執(zhí)行某查詢命令。
例如/*force_node='pi-bpxxxxxxxx'*/ show processlist
,該show processlist
命令只在pi-bpxxxxxxxx
節(jié)點執(zhí)行。如果該節(jié)點發(fā)生故障,則返回報錯force hint server node is not found, please check.
。
支持在SQL語句前加上/*force_proxy_internal*/set force_node = '<節(jié)點ID>'
強制指定在某節(jié)點執(zhí)行所有查詢命令。
例如/*force_proxy_internal*/set force_node = 'pi-bpxxxxxxxx'
,執(zhí)行該命令后,后續(xù)所有查詢命令只發(fā)往pi-bpxxxxxxxx
節(jié)點,如果該節(jié)點發(fā)生故障,則返回報錯set force node 'rr-bpxxxxx' is not found, please check.
。
說明
- 若您需要通過MySQL官方命令行執(zhí)行上述Hint語句,請在命令行中加上-c參數(shù),否則該Hint會被MySQL官方命令行過濾導致Hint失效,具體請參見MySQL官方命令行。
- 通常不建議使用
/*force_proxy_internal*/
語法,會導致后續(xù)所有查詢請求都發(fā)往該節(jié)點,讀寫分離失效。 - Hint語句里不要有改變環(huán)境變量的語句,例如
/*FORCE_SLAVE*/ set names utf8;
等,這類語句可能導致后續(xù)的業(yè)務出錯。
支持在SQL語句前加上/*FORCE_MASTER*/
或/*FORCE_SLAVE*/
強制指定這條SQL的路由方向
所以當我們使用 orm 庫的時候,就需要有一個類似鉤子的東西,能在執(zhí)行之前想辦法將 sql 改寫為所需要的樣子,這就是今天的需求。
嘗試過程
如果你只想知道如何使用,可跳過本段,直接去看最后的實現(xiàn)部分
一開始我做了各種嘗試,由于 xorm 本身其實并沒有相關(guān)文檔說明,尋找并嘗試了半天,雖然最后實現(xiàn)了,但是路徑比較曲折。
嘗試 1 ContextHook
最開始我想到的就是肯定是 Hook,不錯,如我所料,確實有 Hook,并且里面有執(zhí)行的 SQL,我非常高興,然后直接開干。
// Hook represents a hook behaviour type Hook interface { BeforeProcess(c *ContextHook) (context.Context, error) AfterProcess(c *ContextHook) error } // ContextHook represents a hook context type ContextHook struct { start time.Time Ctx context.Context SQL string // log content or SQL Args []interface{} // if it's a SQL, it's the arguments Result sql.Result ExecuteTime time.Duration Err error // SQL executed error }
于是我直接實現(xiàn)了一個自定義的 Hook 然后使用 BeforeProcess
方法,在執(zhí)行 SQL
前,替換了 ContextHook
其中的 SQL
代碼非常簡單,我就不展示了,然后調(diào)試了半天,發(fā)現(xiàn)打印的 SQL 已經(jīng)被改寫了,但實際執(zhí)行卻還是原來的 SQL。
為什么?于是我去翻了源碼,發(fā)現(xiàn),見鬼,這個 ContextHook
里面的 SQL 僅僅是為了日志打印用的。也就是說,這個 Hook 其實目的很明確,就是為了打印日志和計算 SQL 執(zhí)行時間用的。
嘗試 2 Events
在嘗試 Event 之前我其實找了很多曲線救國的方式,但確實實現(xiàn)不了。然后我在文檔里面找到了 Events。
比如:BeforeUpdate()
BeforeDelete()
等等。問題是,Event 無法獲取到需要執(zhí)行的 SQL,事件僅能拿到需要執(zhí)行的條件,而還沒有解析成 SQL,所以這個方案也不行
嘗試 3 Filter
于是我翻遍了源碼,看看源碼之前到底有什么操作能幫助我來完成這件事,然后發(fā)現(xiàn)了 Filter
// Filter is an interface to filter SQL type Filter interface { Do(sql string) string }
Filter 原本的作用是幫助 dialect 去過濾一些特殊數(shù)據(jù)庫的特殊 SQL 來幫助 xorm 來適配各種類型的數(shù)據(jù)庫。我發(fā)現(xiàn)在 SQL 執(zhí)行之前,只有它能獲取到 SQL 并改寫,并且改寫后的 SQL 能被執(zhí)行。但,你從上面的接口也看到了,F(xiàn)ilter 除了 SQL,其他什么也沒有。于是我其實返回去嘗試了很多其他的解法,發(fā)現(xiàn)仍然無解,最后去官方倉庫提交了 PR,將 context 信息傳遞了進去,至此,就有了后面的實現(xiàn)。
實現(xiàn)
首先需要自定義 Dialect 和 Filter,因為 go 沒有繼承,所以使用組合的方式來實現(xiàn)多態(tài),將原來的 dialects.Dialect 定義包裝,并重寫 Filters 方法用于獲取到我們自定義的 Filter。
注意,mysql 默認是沒有 Filter 的,其他數(shù)據(jù)庫可能存在 Filter,可能需要將原來的拿過來并在末尾 append 一個自定義的 Filter。替換 SQL 就很簡單了,你只需要按照你的需求,改寫 SQL 并返回就可以了。如果你和我一樣需要額外的信息,可以從 context 中獲取,比如傳遞用戶信息,或者 id,用于分庫分表或?qū)崿F(xiàn)多租戶等。
type MyDialect struct { dialects.Dialect } func (d *MyDialect) Filters() []dialects.Filter { return []dialects.Filter{&MyFilter{}} } type MyFilter struct { } func (m *MyFilter) Do(ctx context.Context, sql string) string { return "/** 獲取信息,改寫sql **/" + sql }
然后 xorm 只有 NewEngineWithDialectAndDB
方法執(zhí)行自定義 Dialect
,所以用這個方法創(chuàng)建 Engine。并且使用 OpenDialect 方法將默認原先 xorm 的 mysql 對應的 Dialect 拿出來封裝成自己的。
driver := "mysql" connection := "root:root@tcp(127.0.0.1:3306)/test?charset=utf8mb4" dialect, err := dialects.OpenDialect(driver, connection) if err != nil { panic(err) } s := &MyDialect{Dialect: dialect} dbs, err := core.Open(driver, connection) if err != nil { panic(err) } engine, err := xorm.NewEngineWithDialectAndDB(driver, connection, s, dbs) if err != nil { panic(err) }
總結(jié)
其實總的實現(xiàn)并不難,但過程還是異常艱辛,不過好在后面的路都很順暢了,有了 SQL 你就可以解析它,比如解析需要操作的表名和操作語句,查詢走 A,插入走 B 等等。最后我碼住一些 Golang 的 MySQL proxy,或許你也需要。PS:目前我沒有使用以下的庫,僅僅是將抽離了下面的幾個庫里面的協(xié)議部分,偽造了 MySQL 服務來使用。
以上就是詳解go如何使用xorm在執(zhí)行前改寫 SQL的詳細內(nèi)容,更多關(guān)于go xorm改寫SQL的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go實現(xiàn)分布式系統(tǒng)高可用限流器實戰(zhàn)
這篇文章主要為大家介紹了Go實現(xiàn)分布式系統(tǒng)高可用限流器實戰(zhàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06golang中使用proto3協(xié)議導致的空值字段不顯示的問題處理方案
這篇文章主要介紹了golang中使用proto3協(xié)議導致的空值字段不顯示的問題處理方案,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-10-10