Ce que vous avez est un impasse . Dans le pire des cas, vous avez 15 goroutines contenant 15 connexions à la base de données, et toutes ces 15 goroutines nécessitent une nouvelle connexion pour continuer. Mais pour obtenir une nouvelle connexion, il faudrait avancer et libérer une connexion :deadlock.
L'article de wikipedia lié détaille la prévention des blocages. Par exemple, une exécution de code ne doit entrer dans une section critique (qui verrouille les ressources) que lorsqu'elle dispose de toutes les ressources dont elle a besoin (ou aura besoin). Dans ce cas, cela signifie que vous devrez réserver 2 connexions (exactement 2 ; si une seule est disponible, laissez-la et attendez), et si vous avez ces 2 connexions, procédez ensuite aux requêtes. Mais dans Go, vous ne pouvez pas réserver de connexions à l'avance. Ils sont alloués selon les besoins lorsque vous exécutez des requêtes.
Généralement, ce schéma doit être évité. Vous ne devez pas écrire de code qui réserve d'abord une ressource (finie) (connexion à la base de données dans ce cas), et avant de la libérer, il en demande une autre.
Une solution de contournement simple consiste à exécuter la première requête, à enregistrer son résultat (par exemple dans une tranche Go), et lorsque vous avez terminé, passez aux requêtes suivantes (mais n'oubliez pas non plus de fermer sql.Rows
première). De cette façon, votre code n'a pas besoin de 2 connexions en même temps.
Et n'oubliez pas de gérer les erreurs ! Je les ai omis par souci de brièveté, mais vous ne devriez pas dans votre code.
Voici à quoi cela pourrait ressembler :
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")
}
}()
Notez que rows.Close()
doit être exécuté comme un defer
déclaration pour s'assurer qu'elle sera exécutée (même en cas de panique). Mais si vous utilisez simplement defer rows.Close()
, qui ne serait exécuté qu'après l'exécution des requêtes suivantes, il n'empêchera donc pas le blocage. Je le refactoriserais donc pour l'appeler dans une autre fonction (qui peut être une fonction anonyme) dans laquelle vous pouvez utiliser 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)
}
}()
Notez également que dans le deuxième for
boucler une instruction préparée (sql.Stmt
) acquis par DB.Prepare()
serait probablement un bien meilleur choix pour exécuter la même requête (paramétrée) plusieurs fois.
Une autre option consiste à lancer des requêtes ultérieures dans de nouvelles goroutines afin que la requête exécutée dans celle-ci puisse se produire lorsque la connexion actuellement verrouillée est libérée (ou toute autre connexion verrouillée par une autre goroutine), mais sans synchronisation explicite, vous n'avez pas le contrôle quand ils se font exécuter. Cela pourrait ressembler à ceci :
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")
}
}()
Pour que votre programme attende également ces goroutines, utilisez le WaitGroup
vous avez déjà en action :
// Pass something if needed
wg.Add(1)
go func() {
defer wg.Done()
db.Exec("SELECT * FROM reviews LIMIT 1")
}()