Работали ли вы с 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. Это позволяет получить полный контроль над запросами и оптимизировать их под конкретные задачи.