sql >> Databasteknik >  >> RDS >> PostgreSQL

Goroutiner blockerade anslutning pool

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")
        }()



  1. Django AttributeError 'float'-objekt har inget attribut 'split'

  2. break row_number() sekvens baserad på flaggvariabel

  3. Metadata om PL/SQL-posttyper på paketnivå

  4. Arvssäkerhetsregler som har brutits efter typ:'MySql.Data.Entity.MySqlEFConfiguration'