Как избежать закрытия SQL-соединения, если тысячи Goroutine пытаются получить доступ к базе данных - PullRequest
0 голосов
/ 15 октября 2019

У меня в таблице глав около 2000000 строк, я хочу обновить каждую строку для определенных условий:

func main(){
    rows, err := db.Query("SELECT id FROM chapters where title = 'custom_type'")
    if err != nil {
       panic(err)
    }

    for rows.Next() {
        var id int
        _ = rows.Scan(&id)
        fmt.Println(id)
        go updateRowForSomeReason(id)
    }
}

func updateRowForSomeReason(id int) {
    row, err := db.Query(fmt.Sprintf("SELECT id FROM chapters where parent_id = %v", id))  
    if err != nil {
        panic(err)  <----- // here is the panic occurs 
    }
    for rows.Next() {
       // ignore update code for simplify
    }
}

Внутри updateRowForSomeReason я выполняю инструкцию обновления для каждой строки.

Работает с несколькими секундами, после этого выводится ошибка:

323005 
323057 
323125 
323244 
323282 
323342 
323459 
323498 
323556 
323618 
323693 
325343 
325424 
325468 
325624 
325816 
326001 
326045 
326082 
326226 
326297 
panic: sql: database is closed

1 Ответ

2 голосов
/ 15 октября 2019

Это не проблема Go, как таковая, скорее вопрос о том, как оптимально структурировать ваш SQL в вашем коде. Вы берете набор результатов, выполняя запрос для 2 000 000 строк:

rows, err := db.Query("SELECT id FROM chapters where title = 'custom_type'")

, а затем выполняете другой запрос для каждой строки в этом наборе результатов:

row, err := db.Query(fmt.Sprintf("SELECT id FROM chapters where parent_id = %v", id))  

и затем выполнить еще немного кода для каждого из этих , по-видимому, один за другим:

for rows.Next() {
   // ignore update code for simplify
}

Это фактически два уровня вложенности операторов, что является очень неэффективным способом загрузки всехэти результаты попадают в программную память, а затем выполняются независимые операторы UPDATE:

SELECT
     +---->SELECT
                +---->UPDATE

Вместо этого вы могли бы выполнять всю работу в самой базе данных, что было бы гораздо более эффективным. Вы не показываете, что такое оператор UPDATE, но это ключевая часть. Допустим, вы хотите установить флаг publish. Вы можете сделать что-то вроде этого:

UPDATE chapters
    SET publish=true
    WHERE parent_id in
        (SELECT id FROM chapters
         WHERE title='custom_type')
    RETURNING id;

Используя вложенный запрос, вы можете объединить все из трех отдельных запросов в один запрос. База данных содержит всю информацию, необходимую для оптимизации операции и построения наиболее эффективного плана запросов, и вы только выполняете операцию single db.Query. Предложение RETURNING позволяет получить список идентификаторов, которые были обновлены в операции. Таким образом, код будет таким простым:

func main(){
    rows, err := db.Query("UPDATE chapters SET publish=true WHERE parent_id in" +
                          "(SELECT id FROM chapters WHERE title='custom_type')" +
                          "RETURNING id;")
    if err != nil {
       panic(err)
    }

    for rows.Next() {
        var id int
        _ = rows.Scan(&id)
        fmt.Println(id)
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...