1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/dana-go-database-sql-tutorial-translation

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
04.0.retrieving.md 11 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 02.03.2025 18:11 571ab8e
layout title
article
Получение наборов результатов

Существуют несколько типичных операций для получения данных из хранилища.

  1. Выполнение запроса, который возвращает строки.
  2. Подготовка заявки для повторного использования, выполнение её несколько раз и её уничтожение.
  3. Выполнение заявки единоразово, без подготовки её для повторного использования.
  4. Выполнение запроса, который возвращает одну строку. Для этого случая существует сокращённый вариант.

Имена функций пакета database/sql Go имеют важное значение. Если имя функции включает Query, это значит, что она предназначена для отправки запроса базе данных и вернёт множество строк, даже если они пусты. Заявки, которые не возвращают строки, должны использовать функции Exec(), а не Query.

Получение данных из базы данных

Рассмотрим пример того, как выполнять запрос к базе данных и работать с результатами. Мы запросим таблицу users за пользователя, чей id равен 1, и выведем его id и name. Будем присваивать результаты переменным, строка за строкой, используя rows.Scan().

var (
    id int
    name string
)
rows, err := db.Query("select id, name from users where id = ?", 1)
if err != nil {
    log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
    err := rows.Scan(&id, &name)
    if err != nil {
        log.Fatal(err)
    }
    log.Println(id, name)
}
err = rows.Err()
if err != nil {
    log.Fatal(err)
}

Вот что происходит в вышеуказанном коде:

  1. Мы используем db.Query() для отправки запроса в базу данных. Проверяем ошибки, как обычно.
  2. Откладываем вызов rows.Close(). Это очень важно.
  3. Проходимся по строкам с помощью rows.Next().
  4. Читаем столбцы каждой строки в переменные с помощью rows.Scan().
  5. Проверяем ошибки после завершения прохода по строкам.

Это практически единственный способ сделать это в Go. Например, вы не можете получить строку как карту значений. Это потому, что всё строго типизировано. Вам нужно создать переменные правильного типа и передать указатели на них, как показано.

Несколько частей этого легко запутаться и могут иметь неблагоприятные последствия.

  • Всегда проверяйте ошибки в конце цикла for rows.Next(). Если возникает ошибка во время цикла, вам нужно знать об этом. Не просто предполагайте, что цикл продолжается до тех пор, пока все строки не будут обработаны.
  • Во-вторых, пока открыт набор результатов (представленный rows), нижележащее соединение занято и не может использоваться для других запросов. Это означает, что оно недоступно в пуле соединений. Если вы пройдёте через все строки с помощью rows.Next(), рано или поздно вы прочитаете последнюю строку, и rows.Next() встретит внутреннюю ошибку конца файла и закроет rows.Close() для вас. Но если по какой-либо причине вы покидаете этот цикл — ранний выход или что-то ещё — то rows не будет закрыт, и соединение останется открытым. (Оно автоматически закрывается, если rows.Next() возвращает ложь из-за ошибки). Это простой способ исчерпания ресурсов.
  • rows.Close() является безопасной операцией, которая ничего не делает, если rows уже закрыта, поэтому вы можете вызывать её несколько раз. Обратите внимание, однако, что мы сначала проверяем ошибки и только затем вызываем rows.Close(), чтобы избежать паники во время выполнения.
  • Всегда используйте defer rows.Close(), даже если вы также явно вызываете rows.Close() в конце цикла, что не так уж плохо.
  • Не используйте defer внутри цикла. Отложенный вызов выполняется только при выходе из функции, поэтому длинноиграющая функция не должна использовать его. Если вы сделаете это, вы будете медленно накапливать память. Если вы повторно запрашиваете и потребляете наборы результатов внутри цикла, вы должны явно вызвать rows.Close() каждый раз, когда закончите с каждым результатом, и не использовать defer.

Как работает Scan()

Когда вы проходите по строкам и сканируете их в целевые переменные, Go выполняет преобразования типов данных за вас, в фоновом режиме. Это основано на типе целевой переменной. Быть осведомлённым об этом может очистить ваш код и помочь избежать повторяющейся работы.

Например, предположим, что вы выбираете некоторые строки из таблицы, определённой с колонками типа строк, таких как VARCHAR(45) или подобные. Однако вы знаете, что таблица всегда содержит числа. Если вы передадите указатель на строку, Go скопирует байты в строку. Теперь вы можете использовать strconv.ParseInt() или подобное для преобразования значения в число. Вам придётся проверять ошибки в операциях SQL, а также ошибки парсинга целого числа. Это мешает и затрудняет работу.

Или же вы можете просто передать Scan() указатель на целое число. Go обнаружит это и вызовет strconv.ParseInt() за вас. Если возникнет ошибка преобразования, вызов Scan() вернёт её. Ваш код теперь более аккуратный и компактный. Это рекомендованный способ использования database/sql.

Подготовка запросов

Общими правилами следует всегда готовить запросы для повторного использования. Результат подготовки запроса представляет собой подготовленное заявление, которое может содержать плейсхолдеры (также известные как привязанные значения) для параметров, которые вы предоставите при выполнении заявления. Это намного лучше, чем конкатенация строк, по всем обычным причинам (например, защита от атак SQL-инъекций).

В MySQL плейсхолдер представлен символом ?, а в PostgreSQL он имеет вид $N, где N — это номер. SQLite принимает любой из этих вариантов. В Oracle плейсхолдеры начинаются с двоеточия и имеют имена, такие как :param1. Мы будем использовать ?, поскольку используем MySQL в качестве нашего примера.

stmt, err := db.Prepare("select id, name from users where id = ?")
if err != nil {
    log.Fatal(err)
}
defer stmt.Close()
rows, err := stmt.Query(1)
if err != nil {
    log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
    // ...
}
if err = rows.Err(); err != nil {
    log.Fatal(err)
}
```Под капотом `db.Query()` фактически готовит, выполняет и закрывает подготовленное заявление. Это три поездки к базе данных. Если вы не будете внимательны, вы можете увеличить количество взаимодействий вашего приложения с базой данных втрое! Некоторые драйверы могут избежать этого в конкретных случаях, но не все драйверы это делают. Смотрите [подготовленные заявления](prepared.html) для получения больше информации.


Запросы одной строки
=====================

Если запрос возвращает максимум одну строку, вы можете использовать сокращённый вариант для некоторых длинноватых шаблонов кода:

```go
var name string
err = db.QueryRow("select name from users where id = ?", 1).Scan(&name)
if err != nil {
    log.Fatal(err)
}
fmt.Println(name)
``

Ошибки от запроса откладываются до вызова `Scan()`, и затем возвращаются оттуда. Также можно вызвать `QueryRow()` на подготовленном заявлении:

```go
stmt, err := db.Prepare("select name from users where id = ?")
if err != nil {
    log.Fatal(err)
}
var name string
err = stmt.QueryRow(1).Scan(&name)
if err != nil {
    log.Fatal(err)
}
fmt.Println(name)

Предыдущий: Доступ к базе данных Следующий: Модификация данных и использование транзакций

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/dana-go-database-sql-tutorial-translation.git
git@api.gitlife.ru:oschina-mirror/dana-go-database-sql-tutorial-translation.git
oschina-mirror
dana-go-database-sql-tutorial-translation
dana-go-database-sql-tutorial-translation
master