"Por que usar db.Exec()":
É verdade que você pode usar
db.Exec
e db.Query
alternadamente para executar as mesmas instruções sql, no entanto, os dois métodos retornam diferentes tipos de resultados. Se implementado pelo driver, o resultado retornado de db.Exec
pode dizer quantas linhas foram afetadas pela consulta, enquanto db.Query
retornará o objeto de linhas em vez disso. Por exemplo, digamos que você queira executar um
DELETE
declaração e você quer saber quantas linhas foram excluídas por ela. Você pode fazer isso da maneira correta:res, err := db.Exec(`DELETE FROM my_table WHERE expires_at = $1`, time.Now())
if err != nil {
panic(err)
}
numDeleted, err := res.RowsAffected()
if err != nil {
panic(err)
}
print(numDeleted)
ou a maneira mais detalhada e objetivamente mais cara:
rows, err := db.Query(`DELETE FROM my_table WHERE expires_at = $1 RETURNING *`, time.Now())
if err != nil {
panic(err)
}
defer rows.Close()
var numDelete int
for rows.Next() {
numDeleted += 1
}
if err := rows.Err(); err != nil {
panic(err)
}
print(numDeleted)
Há uma terceira maneira de fazer isso com uma combinação de CTEs postgres,
SELECT COUNT
, db.QueryRow
e row.Scan
mas não acho que um exemplo seja necessário para mostrar o quão irracional seria uma abordagem quando comparada a db.Exec
. Outra razão para usar
db.Exec
sobre db.Query
é quando você não se importa com o resultado retornado, quando tudo que você precisa é executar a consulta e verificar se houve erro ou não. Nesse caso, você pode fazer isso:if _, err := db.Exec(`<my_sql_query>`); err != nil {
panic(err)
}
Por outro lado, você não pode (você pode, mas não deve) fazer isso:
if _, err := db.Query(`<my_sql_query>`); err != nil {
panic(err)
}
Fazendo isso, depois de um tempo, seu programa entrará em pânico com um erro que diz algo semelhante a
too many connections open
. Isso ocorre porque você está descartando o db.Rows
retornado valor sem primeiro fazer o Close
obrigatório ligue para ele, e assim você acaba com o número de conexões abertas subindo e eventualmente atingindo o limite do servidor. "ou declarações preparadas em Golang?":
Acho que o livro que você citou não está correto. Pelo menos para mim parece ou não um
db.Query
call cria uma nova instrução preparada toda vez que depende do driver que você está usando. Veja por exemplo estas duas seções de
queryDC
(um método não exportado chamado por db.Query
):sem declaração preparada e com declaração preparada. Independentemente de o livro estar correto ou não, um
db.Stmt
criado por db.Query
seria, a menos que haja algum cache interno acontecendo, jogado fora depois que você fechar as Rows
retornadas objeto. Se você chamar manualmente db.Prepare
e, em seguida, armazenar em cache e reutilizar o db.Stmt
retornado você pode potencialmente melhorar o desempenho das consultas que precisam ser executadas com frequência. Para entender como uma declaração preparada pode ser usada para otimizar o desempenho, você pode dar uma olhada na documentação oficial:https://www.postgresql.org/docs/current/static/sql-prepare.html