sql >> Database teknologi >  >> RDS >> PostgreSQL

Goroutiner blokerede forbindelsespool

Det, du har, er en deadlock . I værste fald har du 15 goroutiner med 15 databaseforbindelser, og alle disse 15 goroutiner kræver en ny forbindelse for at fortsætte. Men for at få en ny forbindelse, ville man være nødt til at rykke frem og frigive en forbindelse:dødvande.

Den linkede wikipedia-artikel beskriver forebyggelse af dødvande. For eksempel bør en kodeudførelse kun gå ind i en kritisk sektion (der låser ressourcer), når den har alle de ressourcer, den har brug for (eller skal bruge). I dette tilfælde betyder det, at du skal reservere 2 forbindelser (præcis 2; hvis kun 1 er tilgængelig, så lad den stå og vent), og hvis du har de 2, skal du først fortsætte med forespørgslerne. Men i Go kan du ikke reservere forbindelser på forhånd. De tildeles efter behov, når du udfører forespørgsler.

Generelt bør dette mønster undgås. Du bør ikke skrive kode, som først reserverer en (endelig) ressource (db-forbindelse i dette tilfælde), og før den frigiver den, kræver den en anden.

En nem løsning er at udføre den første forespørgsel, gemme resultatet (f.eks. i en Go-slice), og når du er færdig med det, så fortsæt med de efterfølgende forespørgsler (men glem også ikke at lukke sql.Rows først). På denne måde behøver din kode ikke 2 forbindelser på samme tid.

Og glem ikke at håndtere fejl! Jeg har udeladt dem for kortheds skyld, men du skal ikke i din kode.

Sådan kunne det se ud:

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

Bemærk, at rows.Close() skal udføres som en defer erklæring for at sikre, at den bliver udført (selv i tilfælde af panik). Men hvis du blot bruger defer rows.Close() , som kun vil blive udført efter de efterfølgende forespørgsler er udført, så det forhindrer ikke dødvandet. Så jeg ville ændre det til at kalde det i en anden funktion (som kan være en anonym funktion), hvor du kan bruge 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)
        }
    }()

Bemærk også, at i den anden for loop en forberedt erklæring (sql.Stmt ) erhvervet af DB.Prepare() ville sandsynligvis være et meget bedre valg at udføre den samme (parametriserede) forespørgsel flere gange.

En anden mulighed er at starte efterfølgende forespørgsler i nye goroutiner, så den forespørgsel, der udføres i det, kan ske, når den aktuelt låste forbindelse frigives (eller enhver anden forbindelse, der er låst af enhver anden goroutine), men uden eksplicit synkronisering har du ikke kontrol over, hvornår de bliver henrettet. Det kunne se sådan ud:

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

For at få dit program til også at vente på disse goroutiner, brug WaitGroup du allerede har i aktion:

        // Pass something if needed
        wg.Add(1)
        go func() {
            defer wg.Done()
            db.Exec("SELECT * FROM reviews LIMIT 1")
        }()



  1. Forudbestilling af en GROUP BY-erklæring

  2. Beregn procent stigning/fald fra forrige rækkeværdi

  3. Operand type clash:varchar er inkompatibel med varchar(50), der forsøger at indsætte i krypteret database

  4. vælg forespørgsel for kategoridata med overordnet underordnet forhold