Go: Operações com bancos de dados em Go

O pacote database/sql do Go oferece uma interface unificada para operações com bancos de dados SQL — é possível alternar entre MySQL, SQLite e PostgreSQL simplesmente trocando o driver.

Quando você precisa trabalhar com bancos de dados de forma segura — para evitar injeção de SQL, gerenciar transações e configurar pools de conexão —, a biblioteca padrão database/sql oferece um conjunto completo e seguro de ferramentas.

1. Você aprenderá


2. A história real de um engenheiro de backend

(1) Problema: a concatenação de strings no SQL resultou na exclusão de um banco de dados por meio de um ataque de injeção realizado por um hacker

Bob é engenheiro de back-end em uma plataforma de comércio eletrônico. Ele precisa implementar uma API de busca de produtos:

“Usei fmt.Sprintf para criar uma consulta SQL para produtos: SELECT * FROM products WHERE name LIKE '%" + search + "%'. No segundo dia após o lançamento, alguém digitou ' OR 1=1; DROP TABLE products;-- na caixa de pesquisa. Minha tabela de produtos tinha sumido. Meu chefe perguntou: ‘Para onde foram os 100 mil produtos do banco de dados?’”

GO
// Bad code: string concatenation SQL, fatal vulnerability
func searchProducts(w http.ResponseWriter, r *http.Request) {
    search := r.URL.Query().Get("q")
    // If search = "' OR 1=1; DROP TABLE products;--"
    // Final SQL: SELECT * FROM products WHERE name LIKE '%' OR 1=1; DROP TABLE products;--%'
    query := fmt.Sprintf("SELECT * FROM products WHERE name LIKE '%%%s%%'", search)
    rows, err := db.Query(query)  // Nightmare begins
}

(2) Solução em Go: PreparedStatement pré-compilado

GO
// Good code: precompiled, parameters automatically escaped
func searchProducts(w http.ResponseWriter, r *http.Request) {
    search := r.URL.Query().Get("q")

    // ? is a placeholder; the database driver automatically escapes parameters
    // The search value is always treated as a string, never as part of SQL Syntax
    stmt, err := db.Prepare("SELECT * FROM products WHERE name LIKE ?")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer stmt.Close()

    rows, err := stmt.Query("%" + search + "%")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer rows.Close()

    // Process results...
}

(3) Desempenho: Concatenação de strings x SQL pré-compilado

Dimensão Concatenação de strings PreparedStatement
Injeção de SQL ❌ Risco alto ✅ Escapamento automático
Desempenho Análise do SQL por execução ✅ Analisado apenas uma vez; mais rápido nas execuções subsequentes
Legibilidade Confuso ✅ Claro
Tipo de parâmetro Converter tudo para strings ✅ Manter o tipo

3. Conexão com o banco de dados

(1) ▶ Exemplo: Conectando-se ao SQLite

⚙️ Pré-requisito: Executar go get github.com/mattn/go-sqlite3 ⚠️ Observação: o go-sqlite3 requer o CGO; no Windows, é necessário o gcc (MinGW-w64); no macOS/Linux, ele já vem incluído por padrão

GO
package main

import (
    "database/sql"
    "fmt"
    "log"

    _ "github.com/mattn/go-sqlite3"  // Import driver (_ means only init is executed, not directly used)
)

func main() {
    // Open database (actual connection is established on first query)
    db, err := sql.Open("sqlite3", "./test.db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // Test if connection is reachable
    err = db.Ping()
    if err != nil {
        log.Fatal("Cannot connect to database:", err)
    }

    fmt.Println("Database connected successfully")
}
▶ Experimente

(2) ▶ Exemplo: Conectando-se ao MySQL

⚙️ Pré-requisito: Executar go get github.com/go-sql-driver/mysql

GO
package main

import (
    "database/sql"
    "fmt"
    "log"
    "time"

    _ "github.com/go-sql-driver/mysql"
)

func main() {
    // DSN format: user:password@tcp(host:port)/dbname?params
    dsn := "root:password@tcp(127.0.0.1:3306)/shop?charset=utf8mb4&parseTime=true"

    db, err := sql.Open("mysql", dsn)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // Connection pool configuration
    db.SetMaxOpenConns(25)               // Maximum open connections
    db.SetMaxIdleConns(10)               // Maximum idle connections
    db.SetConnMaxLifetime(5 * time.Minute) // Maximum connection lifetime
    db.SetConnMaxIdleTime(2 * time.Minute) // Maximum idle connection lifetime

    if err = db.Ping(); err != nil {
        log.Fatal("Cannot connect to database:", err)
    }
    fmt.Println("MySQL connected successfully")
}
▶ Experimente

(3) Métodos principais em database/sql

Método Finalidade Retorno
sql.Open(driver, dsn) Abre o banco de dados (carregamento diferido) *DB, error
db.Ping() Verifica se a conexão está realmente acessível error
db.Close() Fechar o banco de dados error
db.Exec(sql, args...) Executar INSERT/UPDATE/DELETE Result, error
db.Query(sql, args...) Executa uma instrução SELECT e retorna várias linhas *Rows, error
db.QueryRow(sql, args...) Executa uma instrução SELECT e retorna uma única linha *Row
db.Prepare(sql) Pré-compilar SQL *Stmt, error
🔥 Erro comum: sql.Open não cria, na verdade, uma conexão — ele apenas valida o formato do DSN. A conexão propriamente dita é estabelecida durante o primeiro Ping ou Query / Exec. Portanto, o fato de sql.Open retornar nil error não significa que o banco de dados esteja acessível.


4. Operações CRUD

(1) ▶ Exemplo: Como criar uma tabela e inserir dados

GO
package main

import (
    "database/sql"
    "fmt"
    "log"

    _ "github.com/mattn/go-sqlite3"
)

func main() {
    db, err := sql.Open("sqlite3", "./shop.db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // Create table
    createSQL := `
    CREATE TABLE IF NOT EXISTS products (
        id    INTEGER PRIMARY KEY AUTOINCREMENT,
        name  TEXT NOT NULL,
        price REAL NOT NULL,
        stock INTEGER NOT NULL DEFAULT 0,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    )`
    _, err = db.Exec(createSQL)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Table created successfully")

    // Insert data (Exec returns Result)
    result, err := db.Exec(
        "INSERT INTO products (name, price, stock) VALUES (?, ?, ?)",
        "Laptop", 999.99, 10,
    )
    if err != nil {
        log.Fatal(err)
    }

    id, _ := result.LastInsertId()
    affected, _ := result.RowsAffected()
    fmt.Printf("Inserted: id=%d, rows affected=%d\n", id, affected)
}
▶ Experimente

(2) ▶ Exemplo: Consulta de dados

GO 📖 Somente leitura
package main

import (
    "database/sql"
    "fmt"
    "log"

    _ "github.com/mattn/go-sqlite3"
)

type Product struct {
    ID    int
    Name  string
    Price float64
    Stock int
}

func main() {
    db, err := sql.Open("sqlite3", "./shop.db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // QueryRow: query a single row
    var p Product
    err = db.QueryRow("SELECT id, name, price, stock FROM products WHERE id = ?", 1).
        Scan(&p.ID, &p.Name, &p.Price, &p.Stock)
    if err == sql.ErrNoRows {
        fmt.Println("Product not found")
    } else if err != nil {
        log.Fatal(err)
    } else {
        fmt.Printf("Product: %+v\n", p)
    }

    // Query: query multiple rows
    rows, err := db.Query("SELECT id, name, price, stock FROM products WHERE price < ?", 500)
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()

    for rows.Next() {
        var pr Product
        err := rows.Scan(&pr.ID, &pr.Name, &pr.Price, &pr.Stock)
        if err != nil {
            log.Fatal(err)
        }
        fmt.Printf("  Product: %+v\n", pr)
    }
    // Check if iteration encountered an error
    if err = rows.Err(); err != nil {
        log.Fatal(err)
    }
}
46 linhas de lógica (limite de 40, somente leitura)

(3) Comparação dos métodos CRUD

Operação Método Valor de retorno Caso de uso
Criar db.Exec(INSERT...) Result (LastInsertId + RowsAffected) Inserir/Atualizar/Excluir
Ler uma única linha db.QueryRow(SELECT...).Scan() *Row + Fechamento automático Consulta de uma única linha
Ler várias linhas db.Query(SELECT...); rows.Scan() *Rows (requer iteração + fechamento) Consulta com várias linhas
Atualizar db.Exec(UPDATE...) Result (RowsAffected) Atualizar dados
Excluir db.Exec(DELETE...) Result (Linhas afetadas) Excluir dados
🔥 Erro comum: É preciso chamar rows.Close() — mesmo que você já tenha percorrido todas as linhas em for rows.Next(). Se não for fechada, ocorrerá um vazamento de conexão (a conexão não será devolvida ao pool). Use defer rows.Close() para garantir que ela seja liberada.


5. PreparedStatement

(1) ▶ Exemplo: Inserção em lote pré-compilada

GO
package main

import (
    "database/sql"
    "fmt"
    "log"

    _ "github.com/mattn/go-sqlite3"
)

type Product struct {
    Name  string
    Price float64
    Stock int
}

func main() {
    db, err := sql.Open("sqlite3", "./shop.db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // Precompile SQL statement
    stmt, err := db.Prepare("INSERT INTO products (name, price, stock) VALUES (?, ?, ?)")
    if err != nil {
        log.Fatal(err)
    }
    defer stmt.Close()

    // Batch insert (SQL compiled only once)
    products := []Product{
        {"Mouse", 29.99, 100},
        {"Keyboard", 79.99, 50},
        {"Monitor", 299.99, 20},
        {"USB-C Hub", 49.99, 200},
    }

    for _, p := range products {
        result, err := stmt.Exec(p.Name, p.Price, p.Stock)
        if err != nil {
            log.Printf("Insert failed %s: %v", p.Name, err)
            continue
        }
        id, _ := result.LastInsertId()
        fmt.Printf("Inserted successfully: %s (id=%d)\n", p.Name, id)
    }
}
▶ Experimente

(2) SQL pré-compilado x SQL concatenado

Comparação Pré-compilação Prepare Concatenação de SQL
Injeção de SQL ✅ Escapamento automático de parâmetros ❌ Alto risco
Desempenho (execuções múltiplas) ✅ Analisar apenas uma vez ❌ Analisar a cada vez
Legibilidade do código Clara (os parâmetros usam “?” como placeholders) Confusa (aspas aninhadas)
Segurança de tipos ✅ Manter os tipos dos parâmetros ❌ Converter tudo em strings
Cenários aplicáveis Todas as entradas do usuário Apenas SQL estático (nomes de tabelas/nomes de colunas, não entradas do usuário)

6. Transações

(1) ▶ Exemplo: Transferência de transação

GO 📖 Somente leitura
package main

import (
    "database/sql"
    "fmt"
    "log"

    _ "github.com/mattn/go-sqlite3"
)

func transferFunds(db *sql.DB, fromID, toID int, amount float64) error {
    // Begin transaction
    tx, err := db.Begin()
    if err != nil {
        return fmt.Errorf("failed to begin transaction: %w", err)
    }
    // Rollback on transaction failure
    defer tx.Rollback()  // If Commit succeeds, Rollback is a no-op

    // 1. Deduct from fromID
    result, err := tx.Exec(
        "UPDATE accounts SET balance = balance - ? WHERE id = ? AND balance >= ?",
        amount, fromID, amount,
    )
    if err != nil {
        return fmt.Errorf("deduction failed: %w", err)
    }
    affected, _ := result.RowsAffected()
    if affected == 0 {
        return fmt.Errorf("insufficient balance or account does not exist")
    }

    // 2. Add to toID
    result, err = tx.Exec(
        "UPDATE accounts SET balance = balance + ? WHERE id = ?",
        amount, toID,
    )
    if err != nil {
        return fmt.Errorf("credit failed: %w", err)
    }
    affected, _ = result.RowsAffected()
    if affected == 0 {
        return fmt.Errorf("recipient account does not exist")
    }

    // Commit transaction
    return tx.Commit()
}

func main() {
    db, err := sql.Open("sqlite3", "./bank.db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // Initialize accounts
    db.Exec("CREATE TABLE IF NOT EXISTS accounts (id INTEGER PRIMARY KEY, balance REAL)")
    db.Exec("INSERT OR IGNORE INTO accounts VALUES (1, 1000.00)")
    db.Exec("INSERT OR IGNORE INTO accounts VALUES (2, 500.00)")

    // Transfer 200 from account 1 to 2
    err = transferFunds(db, 1, 2, 200)
    if err != nil {
        log.Printf("Transfer failed: %v\n", err)
    } else {
        fmt.Println("Transfer successful!")
    }
}
53 linhas de lógica (limite de 40, somente leitura)
100%
sequenceDiagram
    participant App as Application
    participant DB as Database

    App->>DB: BEGIN
    DB-->>App: OK
    App->>DB: UPDATE SET balance = balance - ? WHERE id = 1
    DB-->>App: 1 row affected
    App->>DB: UPDATE SET balance = balance + ? WHERE id = 2
    DB-->>App: 1 row affected
    App->>DB: COMMIT
    DB-->>App: OK (persisted)
    Note over App,DB: If error midway → ROLLBACK<br/>All changes reverted
💡 Dica: Usar defer tx.Rollback() em uma transação é um padrão seguro — se Commit for bem-sucedido, a chamada a Rollback é segura (uma operação nula). Se Commit falhar, defer reverterá automaticamente a transação. Nunca omita o Rollback!


7. Configuração do pool de conexões

GO
package main

import (
    "database/sql"
    "fmt"
    "time"

    _ "github.com/go-sql-driver/mysql"
)

func configurePool(db *sql.DB) {
    // Maximum open connections (new requests queue when this limit is reached)
    db.SetMaxOpenConns(25)

    // Maximum idle connections (kept open but unused)
    db.SetMaxIdleConns(10)

    // Maximum connection lifetime (prevents long-running connections from being dropped by the database)
    db.SetConnMaxLifetime(5 * time.Minute)

    // Maximum idle connection lifetime
    db.SetConnMaxIdleTime(2 * time.Minute)
}

func main() {
    db, _ := sql.Open("mysql", "user:pass@/dbname")
    configurePool(db)
    fmt.Println("Connection pool configured")
}

(1) Parâmetros do pool de conexões

Parâmetro Valor padrão Valor recomendado Descrição
SetMaxOpenConns 0 (ilimitado) 25–100 Número máximo de conexões simultâneas
SetMaxIdleConns 2 10–25 Número máximo de conexões inativas (≤ MaxOpenConns)
SetConnMaxLifetime 0 (sem prazo de validade) 5–30 min Tempo máximo de duração da conexão
SetConnMaxIdleTime 0 (sem prazo de validade) 2–5 min Tempo limite de inatividade da conexão
🔥 Erro comum: MaxIdleConns não pode ser maior que MaxOpenConns — o banco de dados/SQL ajustará isso automaticamente. Além disso, não defina MaxOpenConns=0 (ilimitado) — em condições de alta simultaneidade, isso criará um grande número de conexões e sobrecarregará o banco de dados. Sempre defina um limite máximo razoável.


8. Exemplo completo: Gestão de estoque no comércio eletrônico

⚙️ Pré-requisito: Executar go get github.com/mattn/go-sqlite3

GO
// inventory.go
package main

import (
    "database/sql"
    "fmt"
    "log"
    "sync"
    "time"

    _ "github.com/mattn/go-sqlite3"
)

// ---------- Store ----------

type InventoryStore struct {
    db *sql.DB
    mu sync.RWMutex
}

func NewInventoryStore(dbPath string) (*InventoryStore, error) {
    db, err := sql.Open("sqlite3", dbPath)
    if err != nil {
        return nil, fmt.Errorf("failed to open database: %w", err)
    }

    // Connection pool configuration
    db.SetMaxOpenConns(10)
    db.SetMaxIdleConns(5)
    db.SetConnMaxLifetime(5 * time.Minute)

    store := &InventoryStore{db: db}
    if err := store.initSchema(); err != nil {
        return nil, fmt.Errorf("failed to initialize schema: %w", err)
    }
    return store, nil
}

func (s *InventoryStore) initSchema() error {
    schema := `
    CREATE TABLE IF NOT EXISTS products (
        id    INTEGER PRIMARY KEY AUTOINCREMENT,
        name  TEXT NOT NULL,
        price REAL NOT NULL,
        stock INTEGER NOT NULL DEFAULT 0,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    );
    CREATE TABLE IF NOT EXISTS orders (
        id         INTEGER PRIMARY KEY AUTOINCREMENT,
        product_id INTEGER NOT NULL,
        quantity   INTEGER NOT NULL,
        total      REAL NOT NULL,
        status     TEXT NOT NULL DEFAULT 'pending',
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY (product_id) REFERENCES products(id)
    );`
    _, err := s.db.Exec(schema)
    return err
}

// ---------- Product Operations ----------

func (s *InventoryStore) AddProduct(name string, price float64, stock int) (int64, error) {
    result, err := s.db.Exec(
        "INSERT INTO products (name, price, stock) VALUES (?, ?, ?)",
        name, price, stock,
    )
    if err != nil {
        return 0, err
    }
    return result.LastInsertId()
}

func (s *InventoryStore) GetProduct(id int) (product, error) {
    var p product
    err := s.db.QueryRow(
        "SELECT id, name, price, stock, created_at FROM products WHERE id = ?", id,
    ).Scan(&p.ID, &p.Name, &p.Price, &p.Stock, &p.CreatedAt)
    if err == sql.ErrNoRows {
        return p, fmt.Errorf("product not found")
    }
    return p, err
}

type product struct {
    ID        int
    Name      string
    Price     float64
    Stock     int
    CreatedAt time.Time
}

// ---------- Order Operations (transaction) ----------

type OrderRequest struct {
    ProductID int
    Quantity  int
}

func (s *InventoryStore) PlaceOrder(req OrderRequest) (int64, error) {
    s.mu.Lock()         // Prevent overselling: only one Ordem deduction at a time
    defer s.mu.Unlock()

    tx, err := s.db.Begin()
    if err != nil {
        return 0, fmt.Errorf("failed to begin transaction: %w", err)
    }
    defer tx.Rollback() // Safe rollback

    // 1. Query product and lock row (⚠️ SQLite does not support FOR UPDATE; use MySQL/PostgreSQL for true row locking)
    var price float64
    var stock int
    err = tx.QueryRow(
        "SELECT price, stock FROM products WHERE id = ? FOR UPDATE",
        req.ProductID,
    ).Scan(&price, &stock)
    if err == sql.ErrNoRows {
        return 0, fmt.Errorf("product not found")
    }
    if err != nil {
        return 0, err
    }

    // 2. Check stock
    if stock < req.Quantity {
        return 0, fmt.Errorf("insufficient stock: need %d, have %d", req.Quantity, stock)
    }

    // 3. Deduct stock
    result, err := tx.Exec(
        "UPDATE products SET stock = stock - ? WHERE id = ? AND stock >= ?",
        req.Quantity, req.ProductID, req.Quantity,
    )
    if err != nil {
        return 0, err
    }
    affected, _ := result.RowsAffected()
    if affected == 0 {
        return 0, fmt.Errorf("concurrent stock shortage")
    }

    // 4. Create Ordem
    total := price * float64(req.Quantity)
    result, err = tx.Exec(
        "INSERT INTO orders (product_id, quantity, total) VALUES (?, ?, ?)",
        req.ProductID, req.Quantity, total,
    )
    if err != nil {
        return 0, err
    }

    orderID, _ := result.LastInsertId()

    // 5. Commit
    if err := tx.Commit(); err != nil {
        return 0, fmt.Errorf("failed to commit transaction: %w", err)
    }

    return orderID, nil
}

// ---------- Report ----------

func (s *InventoryStore) LowStockReport(threshold int) ([]product, error) {
    rows, err := s.db.Query(
        "SELECT id, name, price, stock, created_at FROM products WHERE stock < ? ORDER BY stock ASC",
        threshold,
    )
    if err != nil {
        return nil, err
    }
    defer rows.Close()

    var products []product
    for rows.Next() {
        var p product
        if err := rows.Scan(&p.ID, &p.Name, &p.Price, &p.Stock, &p.CreatedAt); err != nil {
            return nil, err
        }
        products = append(products, p)
    }
    return products, rows.Err()
}

// ---------- Main ----------

func main() {
    store, err := NewInventoryStore("./inventory.db")
    if err != nil {
        log.Fatal(err)
    }

    // Add products
    laptopID, _ := store.AddProduct("Laptop", 999.99, 10)
    mouseID, _ := store.AddProduct("Mouse", 29.99, 100)
    fmt.Printf("Products added: laptop=%d, mouse=%d\n", laptopID, mouseID)

    // Place Ordem (transaction-safe)
    orderID, err := store.PlaceOrder(OrderRequest{ProductID: int(laptopID), Quantity: 2})
    if err != nil {
        log.Printf("Order failed: %v\n", err)
    } else {
        fmt.Printf("Order placed successfully: Ordem=%d\n", orderID)
    }

    // Low stock report
    lowStock, _ := store.LowStockReport(20)
    fmt.Printf("Low stock products (%d item):\n", len(lowStock))
    for _, p := range lowStock {
        fmt.Printf("  %s: stock=%d\n", p.Name, p.Stock)
    }
}
⚠️ Observação: O SQLite não oferece suporte ao bloqueio de linhas FOR UPDATE — a cláusula é ignorada silenciosamente. O código acima só consegue um bloqueio de linhas efetivo no MySQL/PostgreSQL; no SQLite, a segurança é garantida por meio do mutex mu.Lock(). Para compatibilidade entre bancos de dados, use atualizações condicionais (UPDATE ... WHERE stock >= ?) em vez de bloqueios de linhas.


❓ Perguntas Frequentes

P: Como faço para escolher entre database/sql e um ORM (como o GORM)? R: O GORM é mais rápido para operações CRUD simples. Use database/sql quando precisar de controle refinado sobre o SQL, alta simultaneidade ou consultas complexas. Recomendação: use o GORM para projetos pequenos e database/sql + um construtor de consultas (como squirrel) para projetos grandes.

P: Por que a pré-compilação impede a injeção de SQL? R: A pré-compilação separa a estrutura do SQL dos parâmetros durante a transmissão. O banco de dados primeiro compila o modelo de SQL (para determinar a estrutura sintática) e, em seguida, vincula os parâmetros como dados brutos. Os parâmetros nunca são analisados como sintaxe de SQL — portanto, ' OR 1=1 é apenas uma string e não se tornará uma cláusula WHERE.

P: Como é garantida a conformidade com o modelo ACID para transações em Go? R: Use tx.Rollback() para reverter todas as alterações em caso de erro. defer tx.Rollback() + tx.Commit() é o padrão — se Commit for bem-sucedido, Rollback não executa nenhuma operação; se Commit falhar, a transação é automaticamente revertida. Isso garante a atomicidade.

P: Como devo configurar os parâmetros do pool de conexões? R: Os parâmetros principais são MaxOpenConns (número máximo de conexões simultâneas) e MaxIdleConns (limite de conexões inativas). Recomendamos definir MaxOpenConns como 2 a 3 vezes o número de conexões simultâneas e MaxIdleConns como MaxOpenConns/2. Defina SetConnMaxLifetime como 5 minutos para evitar que as conexões sejam encerradas pelo middleware do banco de dados.

P: O SQLite e o MySQL são usados da mesma forma no Go? R: Basicamente, da mesma forma — ambos são operados por meio da interface database/sql. Diferenças: (1) Placeholders: o SQLite usa ?, o MySQL usa ? e o PostgreSQL usa $1; (2) Diferenças na sintaxe SQL (como a sintaxe para chaves primárias autoincrementais); (3) O desempenho do SQLite em termos de concorrência não é tão bom quanto o do MySQL.

P: Ainda preciso chamar rows.Close() depois de percorrer rows.Next()? R: Sim. Mesmo após percorrer todas as linhas, rows ainda mantém a conexão com o banco de dados. rows.Close() libera a conexão de volta para o pool. Use defer rows.Close() para registrar isso imediatamente após a criação de rows, garantindo que os recursos sejam liberados em qualquer cenário.

P: Como faço para solucionar problemas com consultas SQL lentas? R: (1) Defina db.SetMaxOpenConns e db.SetMaxIdleConns na camada de banco de dados/SQL para garantir que o pool de conexões não se torne um gargalo; (2) Use o log de consultas lentas integrado ao banco de dados; (3) Use EXPLAIN ANALYZE para analisar o plano de consulta; (4) Considere adicionar um middleware de registro de SQL para registrar o tempo de execução de todas as consultas.


📖 Resumo


📝 Exercícios

  1. Problema básico (Dificuldade ⭐): Crie um banco de dados SQLite e crie uma tabela tasks (id, título, concluído, data_de_criação). Implemente três funções: InsertTask, ListTasks (ordenadas por data) e MarkDone (atualização por id).

  2. Problema avançado (Dificuldade ⭐⭐): Implemente a camada de banco de dados para um sistema de blog. Requisitos: (1) Tabela posts (id, title, content, author_id, created_at); (2) Suportar consultas paginadas (LIMIT/OFFSET); (3) Usar instruções preparadas para inserções a fim de evitar ataques de injeção; (4) Transações: atualizar a contagem de postagens do autor quando uma postagem for publicada; (5) Usar -race para verificar a segurança de concorrência.

  3. Desafio (Dificuldade: ⭐⭐⭐): Implemente operações seguras contra concorrência para um sistema de gerenciamento de estoque. Requisitos: (1) 100 goroutines fazem pedidos simultaneamente (cada uma deduzindo o estoque de um item diferente); (2) Use transações do database/sql + mutexes na camada de aplicação para evitar vendas excessivas; (3) Pré-compile todas as instruções SQL; (4) Configure o pool de conexões adequadamente; (5) Conte o número de pedidos bem-sucedidos e com falha e calcule o estoque final.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%