PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Goroutinen haben den Verbindungspool blockiert

Was Sie haben, ist ein Deadlock . Im schlimmsten Fall haben Sie 15 Goroutinen, die 15 Datenbankverbindungen halten, und alle diese 15 Goroutinen erfordern eine neue Verbindung, um fortzufahren. Aber um eine neue Verbindung zu bekommen, müsste man eine Verbindung vorrücken und freigeben:Deadlock.

Der verlinkte Wikipedia-Artikel beschreibt die Vermeidung von Deadlocks. Beispielsweise sollte eine Codeausführung nur dann in einen kritischen Abschnitt (der Ressourcen sperrt) eintreten, wenn sie über alle Ressourcen verfügt, die sie benötigt (oder benötigen wird). Das bedeutet in diesem Fall, dass Sie 2 Verbindungen reservieren müssten (genau 2; wenn nur 1 verfügbar ist, lassen Sie es und warten Sie), und wenn Sie diese 2 haben, fahren Sie erst dann mit den Abfragen fort. In Go können Sie jedoch keine Verbindungen im Voraus reservieren. Sie werden nach Bedarf zugewiesen, wenn Sie Abfragen ausführen.

Generell sollte dieses Muster vermieden werden. Sie sollten keinen Code schreiben, der zuerst eine (endliche) Ressource reserviert (in diesem Fall eine DB-Verbindung) und bevor er sie freigibt, eine andere anfordert.

Eine einfache Problemumgehung besteht darin, die erste Abfrage auszuführen, ihr Ergebnis zu speichern (z. B. in einem Go-Slice) und wenn Sie damit fertig sind, mit den nachfolgenden Abfragen fortzufahren (aber vergessen Sie auch nicht, sql.Rows Erste). Auf diese Weise benötigt Ihr Code nicht 2 Verbindungen gleichzeitig.

Und vergessen Sie nicht, Fehler zu behandeln! Ich habe sie der Kürze halber weggelassen, aber Sie sollten sie nicht in Ihren Code aufnehmen.

So könnte es aussehen:

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

Beachten Sie, dass rows.Close() sollte als defer ausgeführt werden Anweisung, um sicherzustellen, dass es ausgeführt wird (selbst im Falle einer Panik). Aber wenn Sie einfach defer rows.Close() verwenden , die erst ausgeführt werden, nachdem die nachfolgenden Abfragen ausgeführt wurden, sodass der Deadlock nicht verhindert wird. Also würde ich es umgestalten, um es in einer anderen Funktion (die eine anonyme Funktion sein kann) aufzurufen, in der Sie einen defer verwenden können :

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

Beachten Sie auch das im zweiten for Schleife eine vorbereitete Anweisung (sql.Stmt). ) erworben durch DB.Prepare() wäre wahrscheinlich eine viel bessere Wahl, dieselbe (parametrisierte) Abfrage mehrmals auszuführen.

Eine andere Möglichkeit besteht darin, nachfolgende Abfragen in neuen Goroutinen zu starten, sodass die darin ausgeführte Abfrage ausgeführt werden kann, wenn die derzeit gesperrte Verbindung freigegeben wird (oder eine andere Verbindung, die von einer anderen Goroutine gesperrt wird), aber dann haben Sie ohne explizite Synchronisierung keine Kontrolle darüber wann Sie werden hingerichtet. Das könnte so aussehen:

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

Um Ihr Programm auch auf diese Goroutinen warten zu lassen, verwenden Sie die WaitGroup Sie haben bereits in Aktion:

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