Apa yang Anda miliki adalah kebuntuan . Dalam skenario terburuk, Anda memiliki 15 goroutine yang memiliki 15 koneksi database, dan ke-15 goroutine tersebut memerlukan koneksi baru untuk melanjutkan. Tetapi untuk mendapatkan koneksi baru, seseorang harus maju dan melepaskan koneksi:deadlock.
Artikel wikipedia tertaut merinci pencegahan kebuntuan. Misalnya, eksekusi kode hanya boleh memasuki bagian kritis (yang mengunci sumber daya) ketika ia memiliki semua sumber daya yang dibutuhkan (atau akan dibutuhkan). Dalam hal ini, ini berarti Anda harus memesan 2 koneksi (tepatnya 2; jika hanya 1 yang tersedia, tinggalkan dan tunggu), dan jika Anda memiliki 2 koneksi itu, baru lanjutkan dengan kueri. Tetapi di Go Anda tidak dapat memesan koneksi terlebih dahulu. Mereka dialokasikan sesuai kebutuhan saat Anda menjalankan kueri.
Umumnya pola ini harus dihindari. Anda tidak boleh menulis kode yang terlebih dahulu mencadangkan sumber daya (terbatas) (dalam hal ini koneksi db), dan sebelum merilisnya, ia meminta sumber daya yang lain.
Solusi mudah adalah dengan mengeksekusi kueri pertama, simpan hasilnya (misalnya ke dalam irisan Go), dan ketika Anda selesai melakukannya, lanjutkan dengan kueri berikutnya (tetapi juga jangan lupa untuk menutup sql.Rows
pertama). Dengan cara ini kode Anda tidak memerlukan 2 koneksi secara bersamaan.
Dan jangan lupa untuk menangani kesalahan! Saya menghilangkannya untuk singkatnya, tetapi Anda tidak boleh dalam kode Anda.
Seperti inilah tampilannya:
go func() {
defer wg.Done()
rows, _ := db.Query("SELECT * FROM reviews LIMIT 1")
var data []int // Use whatever type describes data you query
for rows.Next() {
var something int
rows.Scan(&something)
data = append(data, something)
}
rows.Close()
for _, v := range data {
// You may use v as a query parameter if needed
db.Exec("SELECT * FROM reviews LIMIT 1")
}
}()
Perhatikan bahwa rows.Close()
harus dieksekusi sebagai defer
pernyataan untuk memastikan itu akan dieksekusi (bahkan jika terjadi kepanikan). Tetapi jika Anda hanya menggunakan defer rows.Close()
, yang hanya akan dieksekusi setelah kueri berikutnya dieksekusi, sehingga tidak akan mencegah kebuntuan. Jadi saya akan refactor untuk memanggilnya di fungsi lain (yang mungkin merupakan fungsi anonim) di mana Anda dapat menggunakan defer
:
rows, _ := db.Query("SELECT * FROM reviews LIMIT 1")
var data []int // Use whatever type describes data you query
func() {
defer rows.Close()
for rows.Next() {
var something int
rows.Scan(&something)
data = append(data, something)
}
}()
Perhatikan juga bahwa di for
kedua loop pernyataan yang sudah disiapkan (sql.Stmt
) diakuisisi oleh DB.Prepare()
mungkin akan menjadi pilihan yang jauh lebih baik untuk mengeksekusi kueri (berparameter) yang sama beberapa kali.
Opsi lainnya adalah meluncurkan kueri berikutnya di goroutine baru sehingga kueri yang dieksekusi di dalamnya dapat terjadi saat koneksi yang saat ini terkunci dilepaskan (atau koneksi lain apa pun yang dikunci oleh goroutine lain), tetapi kemudian tanpa sinkronisasi eksplisit Anda tidak memiliki kontrol kapan mereka dieksekusi. Ini bisa terlihat seperti ini:
go func() {
defer wg.Done()
rows, _ := db.Query("SELECT * FROM reviews LIMIT 1")
defer rows.Close()
for rows.Next() {
var something int
rows.Scan(&something)
// Pass something if needed
go db.Exec("SELECT * FROM reviews LIMIT 1")
}
}()
Untuk membuat program Anda menunggu goroutine ini juga, gunakan WaitGroup
Anda sudah beraksi:
// Pass something if needed
wg.Add(1)
go func() {
defer wg.Done()
db.Exec("SELECT * FROM reviews LIMIT 1")
}()