PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Conjunto de conexões bloqueadas Goroutines


O que você tem é um impasse . Na pior das hipóteses, você tem 15 goroutines mantendo 15 conexões de banco de dados e todas essas 15 goroutines exigem uma nova conexão para continuar. Mas para conseguir uma nova conexão, teria que avançar e liberar uma conexão:deadlock.

O artigo vinculado da wikipedia detalha a prevenção de impasse. Por exemplo, uma execução de código só deve entrar em uma seção crítica (que bloqueia recursos) quando tiver todos os recursos de que precisa (ou precisará). Neste caso, isso significa que você teria que reservar 2 conexões (exatamente 2; se apenas 1 estiver disponível, deixe-o e aguarde), e se você tiver essas 2, só então prossiga com as consultas. Mas em Go você não pode reservar conexões com antecedência. Eles são alocados conforme necessário quando você executa consultas.

Geralmente este padrão deve ser evitado. Você não deve escrever código que primeiro reserve um recurso (finito) (conexão db neste caso), e antes de liberá-lo, ele exige outro.

Uma solução fácil é executar a primeira consulta, salvar seu resultado (por exemplo, em uma fatia Go) e, quando terminar, prosseguir com as consultas subsequentes (mas também não se esqueça de fechar sql.Rows primeiro). Desta forma seu código não precisa de 2 conexões ao mesmo tempo.

E não se esqueça de lidar com erros! Eu os omiti por brevidade, mas você não deveria no seu código.

É assim que pode ficar:
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")
    }
}()

Observe que rows.Close() deve ser executado como um defer para ter certeza de que será executado (mesmo em caso de pânico). Mas se você simplesmente usar defer rows.Close() , que só seria executado após a execução das consultas subsequentes, portanto, não impedirá o deadlock. Então, eu refatoraria para chamá-lo em outra função (que pode ser uma função anônima) na qual você pode usar um 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)
        }
    }()

Observe também que no segundo for loop uma instrução preparada (sql.Stmt ) adquirido por DB.Prepare() provavelmente seria uma escolha muito melhor para executar a mesma consulta (parametrizada) várias vezes.

Outra opção é iniciar consultas subsequentes em novas goroutines para que a consulta executada nela possa acontecer quando a conexão atualmente bloqueada for liberada (ou qualquer outra conexão bloqueada por qualquer outra goroutine), mas sem sincronização explícita você não tem controle quando eles são executados. Poderia ficar assim:
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")
    }
}()

Para fazer seu programa esperar por essas goroutines também, use o WaitGroup você já tem em ação:
        // Pass something if needed
        wg.Add(1)
        go func() {
            defer wg.Done()
            db.Exec("SELECT * FROM reviews LIMIT 1")
        }()