Masz zakleszczenie . W najgorszym przypadku masz 15 gorutyn posiadających 15 połączeń z bazą danych, a wszystkie te 15 gorutyn wymagają nowego połączenia, aby kontynuować. Ale aby uzyskać nowe połączenie, należałoby przejść dalej i zwolnić połączenie:impas.
Powiązany artykuł w Wikipedii zawiera szczegółowe informacje na temat zapobiegania impasowi. Na przykład wykonanie kodu powinno wprowadzić sekcję krytyczną (która blokuje zasoby), gdy ma wszystkie potrzebne zasoby (lub będzie potrzebować). W tym przypadku oznacza to, że musiałbyś zarezerwować 2 połączenia (dokładnie 2; jeśli tylko 1 jest dostępne, zostaw je i poczekaj), a jeśli masz te 2, dopiero wtedy kontynuuj zapytania. Ale w Go nie ma możliwości wcześniejszej rezerwacji połączeń. Są one przydzielane w razie potrzeby podczas wykonywania zapytań.
Generalnie należy unikać tego wzorca. Nie powinieneś pisać kodu, który najpierw rezerwuje (skończony) zasób (w tym przypadku połączenie z bazą danych), a zanim go zwolni, będzie wymagał kolejnego.
Łatwym obejściem jest wykonanie pierwszego zapytania, zapisanie jego wyniku (np. w wycinku Go), a kiedy już to zrobisz, przejdź do kolejnych zapytań (ale nie zapomnij też zamknąć sql.Rows
pierwszy). W ten sposób Twój kod nie wymaga 2 połączeń jednocześnie.
I nie zapomnij poradzić sobie z błędami! Pominąłem je dla zwięzłości, ale nie powinieneś w kodzie.
Tak mogłoby to wyglądać:
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")
}
}()
Zauważ, że rows.Close()
powinien być wykonywany jako defer
oświadczenie, aby upewnić się, że zostanie wykonane (nawet w przypadku paniki). Ale jeśli po prostu użyjesz defer rows.Close()
, który zostałby wykonany dopiero po wykonaniu kolejnych zapytań, więc nie zapobiegnie zakleszczeniu. Więc przekształciłbym go, aby wywołać go w innej funkcji (która może być funkcją anonimową), w której można użyć 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)
}
}()
Zauważ też, że w drugim for
zapętl przygotowaną instrukcję (sql.Stmt
) nabyte przez DB.Prepare()
byłoby prawdopodobnie znacznie lepszym wyborem do wielokrotnego wykonania tego samego (sparametryzowanego) zapytania.
Inną opcją jest uruchamianie kolejnych zapytań w nowych gorutynach, tak aby zapytanie wykonane w tym miejscu mogło nastąpić w momencie zwolnienia aktualnie zablokowanego połączenia (lub innego połączenia zablokowanego przez dowolną inną gorutynę), ale wtedy bez jawnej synchronizacji nie masz kontroli, kiedy zostają straceni. Może to wyglądać tak:
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")
}
}()
Aby Twój program również czekał na te gorutyny, użyj WaitGroup
masz już w akcji:
// Pass something if needed
wg.Add(1)
go func() {
defer wg.Done()
db.Exec("SELECT * FROM reviews LIMIT 1")
}()