Ответ
Да, активно работал с SQL в различных проектах. В Go для взаимодействия с реляционными базами данных используется стандартный пакет database/sql в сочетании с конкретными драйверами для СУБД (например, github.com/lib/pq для PostgreSQL, github.com/go-sql-driver/mysql для MySQL, github.com/mattn/go-sqlite3 для SQLite).
Основные практики и подходы:
-
Подключение к базе данных:
import ( "database/sql" _ "github.com/lib/pq" // Импорт драйвера PostgreSQL "log" ) func connectDB() *sql.DB { connStr := "postgres://user:password@localhost:5432/dbname?sslmode=disable" db, err := sql.Open("postgres", connStr) if err != nil { log.Fatalf("Не удалось подключиться к базе данных: %v", err) } // Проверка соединения if err = db.Ping(); err != nil { log.Fatalf("Не удалось установить соединение с базой данных: %v", err) } log.Println("Успешное подключение к базе данных!") return db } // В main или другом месте: db := connectDB(); defer db.Close() -
Выполнение запросов:
db.Query(): Для выполнения запросовSELECT, возвращающих строки.rows, err := db.Query("SELECT id, name FROM users WHERE age > $1", 25) if err != nil { log.Fatal(err) } defer rows.Close() for rows.Next() { var id int var name string if err := rows.Scan(&id, &name); err != nil { log.Fatal(err) } fmt.Printf("ID: %d, Name: %sn", id, name) } if err = rows.Err(); err != nil { log.Fatal(err) } // Проверка ошибок после циклаdb.Exec(): Для выполнения запросовINSERT,UPDATE,DELETE, не возвращающих строки.result, err := db.Exec("INSERT INTO users(name, email) VALUES($1, $2)", "Alice", "alice@example.com") if err != nil { log.Fatal(err) } rowsAffected, err := result.RowsAffected() if err != nil { log.Fatal(err) } fmt.Printf("Добавлено строк: %dn", rowsAffected)-
Подготовленные выражения (
Prepare()): Для повторяющихся запросов, повышают производительность и безопасность (защита от SQL-инъекций).stmt, err := db.Prepare("UPDATE products SET price = $1 WHERE id = $2") if err != nil { log.Fatal(err) } defer stmt.Close() _, err = stmt.Exec(100.00, 1) if err != nil { log.Fatal(err) } _, err = stmt.Exec(150.50, 2) if err != nil { log.Fatal(err) }
-
Транзакции: Для обеспечения атомарности операций.
tx, err := db.Begin() if err != nil { log.Fatal(err) } _, err = tx.Exec("INSERT INTO accounts(user_id, balance) VALUES($1, $2)", 1, 1000) if err != nil { tx.Rollback() // Откат при ошибке log.Fatal(err) } _, err = tx.Exec("UPDATE users SET has_account = TRUE WHERE id = $1", 1) if err != nil { tx.Rollback() log.Fatal(err) } err = tx.Commit() // Подтверждение транзакции if err != nil { log.Fatal(err) } fmt.Println("Транзакция успешно завершена.")
Мой подход:
В сложных проектах я использую ORM, такие как GORM, для быстрой разработки, упрощения работы с миграциями и базовыми CRUD-операциями. Однако для критичных к производительности участков, сложных аналитических запросов или специфических операций, где ORM может быть неэффективен или генерировать неоптимальный SQL, я предпочитаю писать чистый SQL с использованием database/sql. Это позволяет получить полный контроль над запросами и оптимизировать их под конкретные задачи.
Ответ 18+ 🔞
О, SQL в Go, да? Ну, это как пытаться объяснить бабушке, что такое интернет, только бабушка — это компилятор, а интернет — это твоя база данных, которая в любой момент может накрыться медным тазом.
Смотри, в Go для этого есть стандартный пакет database/sql. Это как базовый набор инструментов: молоток, гвозди, паяльник. А драйвера для конкретных баз — это уже насадки. Для PostgreSQL — lib/pq, для MySQL — go-sql-driver/mysql. Без них ты как с голыми руками, нихуя не сделаешь.
Как обычно всё и происходит, с косяками и приключениями:
-
Подключение — первый бой за доверие. Ты пытаешься достучаться до базы, а она тебе: «А кто ты такой, пидарас шерстяной? Пароль скажи!». Выглядит это примерно так:
import ( "database/sql" _ "github.com/lib/pq" // Вот эта подлянка-драйвер, её вроде и не видно, но без неё — пиздец. "log" ) func connectDB() *sql.DB { // Строка подключения. Один хуй с запятой — и ты уже полчаса ищешь, почему не коннектится. connStr := "postgres://user:password@localhost:5432/mydb?sslmode=disable" db, err := sql.Open("postgres", connStr) if err != nil { log.Fatalf("Не удалось даже открыть соединение, ёпта: %v", err) // Обычно тут опечатка в названии драйвера. } // А вот это Ping — реальная проверка. Open мог и не ошибиться, а тут база может лежать. if err = db.Ping(); err != nil { log.Fatalf("База тебя послала нахуй, соединения нет: %v", err) } log.Println("Ура, подключились! (пока что)") return db } // Не забудь потом db.Close(), а то соединения все повиснут, как сосульки. -
Запросы — тут начинается магия или полный пиздец.
SELECT(чтение): Ты ждёшь данные, а получаешь либо строки, либо ошибку, от которой волосы дыбом.rows, err := db.Query("SELECT id, name FROM users WHERE age > $1", 25) // $1 — это placeholder, красота. if err != nil { log.Fatal(err) } // Типа "ой, всё". defer rows.Close() // ЗАКРЫВАЙ, блядь, rows! Иначе память потечёт, как сито. for rows.Next() { var id int var name string if err := rows.Scan(&id, &name); err != nil { // Scan тыкает данные прямо в переменные. Не перепутай порядок! log.Fatal(err) } fmt.Printf("ID: %d, Name: %sn", id, name) } // Важный момент, про который все забывают, а потом охуевают: if err = rows.Err(); err != nil { log.Fatal(err) } // Проверка ошибок ПОСЛЕ цикла. Вдруг что сломалось в процессе?INSERT/UPDATE/DELETE(запись): ТутExec. Сделал — и похуй, главное сколько строк зацепил.result, err := db.Exec("INSERT INTO users(name, email) VALUES($1, $2)", "Alice", "alice@example.com") if err != nil { log.Fatal(err) } // UNIQUE constraint violation, например. Классика. rowsAffected, err := result.RowsAffected() // Узнаём, скольких мы там задели. if err != nil { log.Fatal(err) } fmt.Printf("Добавлено/изменено строк: %dn", rowsAffected) // Надеемся, что больше нуля.-
Подготовленные выражения (
Prepare): Если один и тот же запрос надо ебашить сто раз. Экономит силы и время.stmt, err := db.Prepare("UPDATE products SET price = $1 WHERE id = $2") if err != nil { log.Fatal(err) } defer stmt.Close() // И это закрывай, да. // А теперь юзаем подготовленное выражение, как автомат. _, err = stmt.Exec(100.00, 1) if err != nil { log.Fatal(err) } _, err = stmt.Exec(150.50, 2) // Быстро и безопасно от инъекций. if err != nil { log.Fatal(err) }
-
Транзакции — высший пилотаж, где можно всё запороть одним махом. Это когда нужно сделать несколько операций так, чтобы либо все выполнились, либо нихуя. Как в жизни не бывает.
tx, err := db.Begin() // Начинаем. Терпения ебать ноль, волнение пиздец. if err != nil { log.Fatal(err) } // Пытаемся что-то сделать. _, err = tx.Exec("INSERT INTO accounts(user_id, balance) VALUES($1, $2)", 1, 1000) if err != nil { tx.Rollback() // Первая же ошибка — откатываем всё к хуям. Жизнь не удалась. log.Fatal(err) } _, err = tx.Exec("UPDATE users SET has_account = TRUE WHERE id = $1", 1) if err != nil { tx.Rollback() // И тут тоже откат. Последовательность, блядь. log.Fatal(err) } err = tx.Commit() // Если дошли сюда — коммитим. Ура, победа! if err != nil { log.Fatal(err) } fmt.Println("Транзакция прошла. Можно выдохнуть.")
А теперь, как я этим пользуюсь на практике, без розовых очков:
Для простых проектов или когда надо быстро накодить — беру ORM, типа GORM. Это как готовый конструктор: собрал модель, и он сам SQL генерит. Удобно, но иногда он выдает такой монструозный запрос, что хочется плакать.
А вот когда дело доходит до чего-то серьёзного, где каждый миллисекунд на счету, или нужен хитрый аналитический запрос на три экрана — тут я сажусь и пишу чистый SQL через database/sql. Потому что ORM в таких случаях начинает выёбываться и делать ересь. Контроль полный, оптимизация ручная. Да, больше кода, зато понимаешь, что именно и как выполняется. Иначе потом дебажить — это просто пиздец, в рот меня чих-пых.