Работали ли вы с SQL?

Ответ

Да, активно работал с SQL в различных проектах. В Go для взаимодействия с реляционными базами данных используется стандартный пакет database/sql в сочетании с конкретными драйверами для СУБД (например, github.com/lib/pq для PostgreSQL, github.com/go-sql-driver/mysql для MySQL, github.com/mattn/go-sqlite3 для SQLite).

Основные практики и подходы:

  1. Подключение к базе данных:

    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()
  2. Выполнение запросов:

    • 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) }
  3. Транзакции: Для обеспечения атомарности операций.

    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. Без них ты как с голыми руками, нихуя не сделаешь.

Как обычно всё и происходит, с косяками и приключениями:

  1. Подключение — первый бой за доверие. Ты пытаешься достучаться до базы, а она тебе: «А кто ты такой, пидарас шерстяной? Пароль скажи!». Выглядит это примерно так:

    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(), а то соединения все повиснут, как сосульки.
  2. Запросы — тут начинается магия или полный пиздец.

    • 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) }
  3. Транзакции — высший пилотаж, где можно всё запороть одним махом. Это когда нужно сделать несколько операций так, чтобы либо все выполнились, либо нихуя. Как в жизни не бывает.

    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 в таких случаях начинает выёбываться и делать ересь. Контроль полный, оптимизация ручная. Да, больше кода, зато понимаешь, что именно и как выполняется. Иначе потом дебажить — это просто пиздец, в рот меня чих-пых.