"¿Por qué incluso usar db.Exec()":
Es cierto que puedes usar db.Exec
y db.Query
indistintamente para ejecutar las mismas sentencias sql; sin embargo, los dos métodos devuelven diferentes tipos de resultados. Si lo implementa el controlador, el resultado devuelto por db.Exec
puede decirle cuántas filas se vieron afectadas por la consulta, mientras que db.Query
devolverá el objeto de filas en su lugar.
Por ejemplo, supongamos que desea ejecutar un DELETE
y desea saber cuántas filas eliminó. Puedes hacerlo de la manera correcta:
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)
o la forma más detallada y objetivamente más costosa:
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)
Hay una tercera forma de hacer esto con una combinación de CTE de postgres, SELECT COUNT
, db.QueryRow
y row.Scan
pero no creo que sea necesario un ejemplo para mostrar cuán irrazonable sería un enfoque en comparación con db.Exec
.
Otra razón para usar db.Exec
sobre db.Query
es cuando no le importa el resultado devuelto, cuando todo lo que necesita es ejecutar la consulta y verificar si hubo un error o no. En tal caso, puede hacer esto:
if _, err := db.Exec(`<my_sql_query>`); err != nil {
panic(err)
}
Por otro lado, no puedes (puedes pero no debes) hacer esto:
if _, err := db.Query(`<my_sql_query>`); err != nil {
panic(err)
}
Al hacer esto, después de un rato, su programa entrará en pánico con un error que dice algo parecido a too many connections open
. Esto se debe a que está descartando las db.Rows
devueltas. valor sin hacer primero el obligatorio Close
llámalo, y así terminas con el número de conexiones abiertas aumentando y eventualmente alcanzando el límite del servidor.
"¿o declaraciones preparadas en Golang?":
No creo que el libro que has citado sea correcto. Al menos a mí me parece si un db.Query
call crea una nueva declaración preparada cada vez que depende del controlador que esté utilizando.
Vea por ejemplo estas dos secciones de queryDC
(un método no exportado llamado por db.Query
):sin sentencia preparada y con sentencia preparada.
Independientemente de si el libro es correcto o no, un db.Stmt
creado por db.Query
sería, a menos que haya algún almacenamiento en caché interno en marcha, descartado después de cerrar las Rows
devueltas objeto. Si, en cambio, llama manualmente a db.Prepare
y luego almacenar en caché y reutilizar el db.Stmt
devuelto puede mejorar potencialmente el rendimiento de las consultas que deben ejecutarse con frecuencia.
Para comprender cómo se puede usar una declaración preparada para optimizar el rendimiento, puede consultar la documentación oficial:https://www.postgresql.org/docs/current/static/sql-prepare.html