Lo que tienes es un punto muerto . En el peor de los casos, tiene 15 gorutinas con 15 conexiones de base de datos, y todas esas 15 gorutinas requieren una nueva conexión para continuar. Pero para obtener una nueva conexión, uno tendría que avanzar y liberar una conexión:interbloqueo.
El artículo de wikipedia vinculado detalla la prevención del interbloqueo. Por ejemplo, la ejecución de un código solo debe ingresar a una sección crítica (que bloquea los recursos) cuando tiene todos los recursos que necesita (o necesitará). En este caso esto significa que tendrías que reservar 2 conexiones (exactamente 2; si solo 1 está disponible, déjalo y espera), y si tienes esas 2, solo entonces continúa con las consultas. Pero en Go no puedes reservar conexiones con antelación. Se asignan según sea necesario cuando ejecuta consultas.
En general, este patrón debe evitarse. No debe escribir código que primero reserve un recurso (finito) (conexión db en este caso), y antes de que lo libere, exija otro.
Una solución sencilla es ejecutar la primera consulta, guardar su resultado (por ejemplo, en un segmento Go) y, cuando haya terminado, continuar con las consultas posteriores (pero tampoco olvide cerrar sql.Rows
primero). De esta manera, su código no necesita 2 conexiones al mismo tiempo.
¡Y no te olvides de manejar los errores! Los omití por brevedad, pero no deberías incluirlos en tu código.
Así es como podría verse:
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")
}
}()
Tenga en cuenta que rows.Close()
debe ejecutarse como defer
declaración para asegurarse de que se ejecutará (incluso en caso de pánico). Pero si simplemente usa defer rows.Close()
, que solo se ejecutaría después de que se ejecuten las consultas posteriores, por lo que no evitará el interbloqueo. Así que lo refactorizaría para llamarlo en otra función (que puede ser una función anónima) en la que puede usar un 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)
}
}()
También tenga en cuenta que en el segundo for
bucle una declaración preparada (sql.Stmt
) adquirido por DB.Prepare()
probablemente sería una opción mucho mejor para ejecutar la misma consulta (parametrizada) varias veces.
Otra opción es lanzar consultas subsiguientes en nuevas gorutinas para que la consulta ejecutada en eso pueda ocurrir cuando se libera la conexión bloqueada actualmente (o cualquier otra conexión bloqueada por cualquier otra gorutina), pero luego, sin sincronización explícita, no tiene control cuando son ejecutados. Podría verse así:
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 hacer que su programa también espere estas rutinas, use el WaitGroup
ya tienes en acción:
// Pass something if needed
wg.Add(1)
go func() {
defer wg.Done()
db.Exec("SELECT * FROM reviews LIMIT 1")
}()