優化併發查詢:如何處理併發統計查詢中的區間鎖

背景是查詢一個有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)進行分組,並在給定的數據區間內進行統計。

以下是三個統計查詢的示例:

  1. 統計mint次數:
sql
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;
  1. 統計轉賬次數:
sql
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;
  1. 統計交互次數:
sql
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框架):

go
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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章