背景是查詢一個有N億條記錄的mysql表
使用go多協程同時查詢一個區間的不同數據代碼:
func txHashesWorker(id int, tasks <-chan Task, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for task := range tasks {
fmt.Printf("%s Process GotTask===> startId:%d, endId:%d \n", time.Now().Format(lib.TIME_FORMAT), task.StartId, task.EndId)
counter := lib.NewCounter(task.SpaceStore)
var wg2 sync.WaitGroup
wg2.Add(3)
// CountChainInteractionCount
go func(task Task) {
defer wg2.Done()
fmt.Printf("%s Process CountChainInteractionCount Start===> startId:%d, endId:%d \n", time.Now().Format(lib.TIME_FORMAT), task.StartId, task.EndId)
web3datas := counter.CountChainInteractionCount(task.Config.Collector.Mysql, task.StartId, task.EndId)
fmt.Printf("%s Process CountChainInteractionCount InsertToWeb3DatasTemp===> web3datas length:%d, \n", time.Now().Format(lib.TIME_FORMAT), len(web3datas))
lib.InsertToWeb3DatasTemp(task.Config.Web3Data.Mysql, web3datas)
fmt.Printf("%s Process CountChainInteractionCount End===> startId:%d, endId:%d \n", time.Now().Format(lib.TIME_FORMAT), task.StartId, task.EndId)
results <- len(web3datas)
}(task)
// CountCreatedNftCount
go func(task Task) {
defer wg2.Done()
fmt.Printf("%s Process CountCreatedNftCount Start===> startId:%d, endId:%d \n", time.Now().Format(lib.TIME_FORMAT), task.StartId, task.EndId)
web3datas := counter.CountCreatedNftCount(task.Config.Collector.Mysql, task.StartId, task.EndId)
fmt.Printf("%s Process CountCreatedNftCount InsertToWeb3DatasTemp===> web3datas length:%d, \n", time.Now().Format(lib.TIME_FORMAT), len(web3datas))
lib.InsertToWeb3DatasTemp(task.Config.Web3Data.Mysql, web3datas)
fmt.Printf("%s Process CountCreatedNftCount End===> startId:%d, endId:%d \n", time.Now().Format(lib.TIME_FORMAT), task.StartId, task.EndId)
results <- len(web3datas)
}(task)
// CountNftTransactionsCount
go func(task Task) {
defer wg2.Done()
fmt.Printf("%s Process DoTask CountNftTransactionsCount Start===> startId:%d, endId:%d \n", time.Now().Format(lib.TIME_FORMAT), task.StartId, task.EndId)
web3datas := counter.CountNftTransactionsCount(task.Config.Collector.Mysql, task.StartId, task.EndId)
fmt.Printf("%s Process DoTask CountNftTransactionsCount InsertToWeb3DatasTemp===> web3datas length:%d, \n", time.Now().Format(lib.TIME_FORMAT), len(web3datas))
lib.InsertToWeb3DatasTemp(task.Config.Web3Data.Mysql, web3datas)
fmt.Printf("%s Process DoTask CountNftTransactionsCount End===> startId:%d, endId:%d \n", time.Now().Format(lib.TIME_FORMAT), task.StartId, task.EndId)
results <- len(web3datas)
}(task)
wg2.Wait()
}
}
這個遇到問題在於3個group同時在一個區間進行,導致促發區間鎖,影響性能,可以採用順序方式執行。
==============================
在數據庫應用程序中,處理併發查詢是一個常見的挑戰。特別是在涉及統計查詢時,可能會出現併發事務對同一數據區間進行修改的情況,導致性能下降。本文將探討如何優化併發查詢,並重點討論處理併發統計查詢中的區間鎖。
假設我們有一個數據庫表tx_hashes,存儲了交易哈希和相關信息。我們希望對該表進行三種統計查詢:統計mint次數、統計轉賬次數和統計交互次數。每個查詢都需要按照遊戲名稱(game_name)和地址(address)進行分組,並在給定的數據區間內進行統計。
以下是三個統計查詢的示例:
- 統計mint次數:
SELECT game_name, address, COUNT(*) AS mint_cnt
FROM tx_hashes
WHERE tx_type = 'mint' AND id >= 1000000 AND id < 1100000
GROUP BY game_name, address;
- 統計轉賬次數:
SELECT game_name, address, COUNT(*) AS transfer_cnt
FROM tx_hashes
WHERE tx_type = 'transfer' AND id >= 1000000 AND id < 1100000
GROUP BY game_name, address;
- 統計交互次數:
SELECT game_name, address, COUNT(*) AS interaction_cnt
FROM tx_hashes
WHERE tx_type = '' AND id >= 1000000 AND id < 1100000
GROUP BY game_name, address;
以上查詢看起來很簡單,但在高併發環境下可能會遇到問題。當多個併發事務同時執行這些查詢時,可能會發生區間鎖(Range Lock)的情況。區間鎖是一種行級鎖,用於保護指定範圍內的數據,以防止併發事務對該範圍內的數據進行修改。
假設有兩個事務同時執行統計查詢,其中一個事務正在統計mint次數,而另一個事務正在統計轉賬次數。由於它們都在相同的數據區間內進行查詢,可能會觸發區間鎖。這樣一來,兩個事務將會相互等待,導致性能下降。
爲了避免區間鎖帶來的併發性能問題,我們可以考慮按順序執行這些查詢,以確保每個查詢在前一個查詢完成後再執行。這樣可以避免併發事務對同一區間的數據進行鎖定,提高併發性能。
以下是按順序執行這些查詢的示例代碼(使用GORM作爲ORM框架):
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// 定義用於存儲統計結果的結構體
type Statistic struct {
GameName string
Address string
MintCount int
TransferCount int
InteractionCnt int
}
func main() {
dsn := "user:password@tcp(localhost:3306)/database"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
fmt.Println(err)
return
}
var statistics []Statistic
// 統計 mint 次數
mintQuery := db.Model(&TxHash{}).
Select("game_name, address, COUNT(*) AS mint_count").
Where("tx_type = 'mint' AND id >= ? AND id < ?", 1000000, 1100000).
Group("game_name, address").
Find(&statistics)
if mintQuery.Error != nil {
fmt.Println(mintQuery.Error)
return
}
// 統計 transfer 次數
transferQuery := db.Model(&TxHash{}).
Select("game_name, address, COUNT(*) AS transfer_count").
Where("tx_type = 'transfer' AND id >= ? AND id < ?", 1000000, 1100000).
Group("game_name, address").
Find(&statistics)
if transferQuery.Error != nil