Wat je hebt is een deadlock . In het ergste geval heb je 15 goroutines met 15 databaseverbindingen, en al die 15 goroutines hebben een nieuwe verbinding nodig om door te gaan. Maar om een nieuwe verbinding te krijgen, zou men moeten doorgaan en een verbinding moeten vrijgeven:deadlock.
Het gekoppelde wikipedia-artikel beschrijft het voorkomen van een impasse. Een code-uitvoering zou bijvoorbeeld alleen een kritieke sectie (die bronnen vergrendelt) moeten binnengaan wanneer deze alle bronnen heeft die het nodig heeft (of nodig zal hebben). In dit geval betekent dit dat u 2 verbindingen moet reserveren (precies 2; als er maar 1 beschikbaar is, laat het staan en wacht), en als u die 2 heeft, ga dan pas verder met de query's. Maar in Go kun je geen verbindingen vooraf reserveren. Ze worden naar behoefte toegewezen wanneer u zoekopdrachten uitvoert.
Over het algemeen moet dit patroon worden vermeden. Je moet geen code schrijven die eerst een (eindige) resource reserveert (db-verbinding in dit geval), en voordat deze deze vrijgeeft, een andere nodig heeft.
Een eenvoudige oplossing is om de eerste query uit te voeren, het resultaat op te slaan (bijvoorbeeld in een Go-segment) en als u daarmee klaar bent, gaat u verder met de volgende query's (maar vergeet ook niet om sql.Rows
eerst). Zo heeft je code niet 2 verbindingen tegelijk nodig.
En vergeet niet om fouten op te lossen! Ik heb ze voor de beknoptheid weggelaten, maar dat moet je niet in je code opnemen.
Zo zou het eruit kunnen zien:
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")
}
}()
Merk op dat rows.Close()
moet worden uitgevoerd als een defer
statement om ervoor te zorgen dat het wordt uitgevoerd (zelfs in geval van paniek). Maar als je gewoon defer rows.Close()
. gebruikt , die alleen zou worden uitgevoerd nadat de volgende query's zijn uitgevoerd, dus het zal de impasse niet voorkomen. Dus ik zou het refactoren om het in een andere functie aan te roepen (wat een anonieme functie kan zijn) waarin je een defer
kunt gebruiken :
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)
}
}()
Merk ook op dat in de tweede for
loop een voorbereide instructie door (sql.Stmt
) verworven door DB.Prepare()
zou waarschijnlijk een veel betere keuze zijn om dezelfde (geparametriseerde) query meerdere keren uit te voeren.
Een andere optie is om volgende query's in nieuwe goroutines te starten, zodat de query die daarin wordt uitgevoerd, kan plaatsvinden wanneer de momenteel vergrendelde verbinding wordt vrijgegeven (of een andere verbinding die is vergrendeld door een andere goroutine), maar dan heb je zonder expliciete synchronisatie geen controle wanneer ze worden geëxecuteerd. Het zou er zo uit kunnen zien:
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")
}
}()
Om uw programma ook op deze goroutines te laten wachten, gebruikt u de WaitGroup
je hebt al in actie:
// Pass something if needed
wg.Add(1)
go func() {
defer wg.Done()
db.Exec("SELECT * FROM reviews LIMIT 1")
}()