Det du har är ett deadlock . I värsta fall har du 15 goroutiner med 15 databasanslutningar, och alla dessa 15 goroutiner kräver en ny anslutning för att fortsätta. Men för att få en ny anslutning skulle man behöva avancera och släppa en anslutning:dödläge.
Den länkade wikipedia-artikeln beskriver förhindrande av dödläge. Till exempel bör en kodexekvering endast gå in i en kritisk sektion (som låser resurser) när den har alla resurser den behöver (eller kommer att behöva). I det här fallet betyder det att du måste reservera 2 anslutningar (exakt 2; om bara 1 är tillgänglig, lämna den och vänta), och om du har dessa 2, fortsätt först sedan med frågorna. Men i Go kan du inte boka anslutningar i förväg. De tilldelas efter behov när du utför frågor.
I allmänhet bör detta mönster undvikas. Du bör inte skriva kod som först reserverar en (ändlig) resurs (db-anslutning i det här fallet), och innan den släpper den kräver den en annan.
En enkel lösning är att köra den första frågan, spara dess resultat (t.ex. i en Go-del), och när du är klar med det, fortsätt sedan med de efterföljande frågorna (men glöm inte att stänga sql.Rows
först). På så sätt behöver inte din kod 2 anslutningar samtidigt.
Och glöm inte att hantera fel! Jag utelämnade dem för korthetens skull, men du bör inte i din kod.
Så här kan det se ut:
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")
}
}()
Observera att rows.Close()
ska köras som en defer
uttalande för att säkerställa att det kommer att köras (även i händelse av panik). Men om du bara använder defer rows.Close()
, som endast skulle köras efter att de efterföljande frågorna har körts, så det kommer inte att förhindra dödläget. Så jag skulle ändra det för att kalla det i en annan funktion (som kan vara en anonym funktion) där du kan använda en 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)
}
}()
Observera också att i den andra for
loopa en förberedd sats (sql.Stmt
) förvärvad av DB.Prepare()
skulle förmodligen vara ett mycket bättre val att köra samma (parameteriserade) fråga flera gånger.
Ett annat alternativ är att starta efterföljande frågor i nya goroutiner så att frågan som körs i det kan ske när den för närvarande låsta anslutningen släpps (eller någon annan anslutning låst av någon annan goroutine), men då utan explicit synkronisering har du inte kontroll när de blir avrättade. Det kan se ut så här:
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")
}
}()
För att få ditt program att vänta på dessa goroutiner också, använd WaitGroup
du redan har i aktion:
// Pass something if needed
wg.Add(1)
go func() {
defer wg.Done()
db.Exec("SELECT * FROM reviews LIMIT 1")
}()