Go: Operações com bancos de dados em Go
O pacote
database/sqldo 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á
database/sql: Registrando drivers e abrindo conexões- Três métodos de consulta:
db.Query,db.QueryRowedb.Exec db.Prepare: Evitar injeção de SQL por meio da pré-compilação- Transação:
Begin/Commit/Rollback - Configuração do pool de conexões:
SetMaxOpenConns/SetMaxIdleConns - Utilização dos drivers do SQLite e do MySQL
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.Sprintfpara 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?’”
// 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
// 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
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")
}
(2) ▶ Exemplo: Conectando-se ao MySQL
⚙️ Pré-requisito: Executar
go get github.com/go-sql-driver/mysql
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")
}
(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 |
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
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)
}
(2) ▶ Exemplo: Consulta de dados
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)
}
}
(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 |
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
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)
}
}
(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
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!")
}
}
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
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
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 |
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
// 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)
}
}
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 (comosquirrel) 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 — seCommitfor bem-sucedido,Rollbacknão executa nenhuma operação; seCommitfalhar, 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
MaxOpenConnscomo 2 a 3 vezes o número de conexões simultâneas eMaxIdleConnscomoMaxOpenConns/2. DefinaSetConnMaxLifetimecomo 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 percorrerrows.Next()? R: Sim. Mesmo após percorrer todas as linhas,rowsainda mantém a conexão com o banco de dados.rows.Close()libera a conexão de volta para o pool. Usedefer rows.Close()para registrar isso imediatamente após a criação derows, 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.SetMaxOpenConnsedb.SetMaxIdleConnsna 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) UseEXPLAIN ANALYZEpara 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
sql.Openabre uma conexão (carregamento diferido) +db.Pingverifica a conectividade- Os três tipos de consultas:
db.Exec,db.Queryedb.QueryRow Prepare: Prevenir injeção de SQL por meio da pré-compilação- Transação:
Begin→Commit/Rollback defer tx.Rollback()Modo de segurança- Conjunto de conexões:
SetMaxOpenConns/SetMaxIdleConns/SetConnMaxLifetime - O SQLite usa
?, o MySQL usa?e o PostgreSQL usa$1
📝 Exercícios
-
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). -
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-racepara verificar a segurança de concorrência. -
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.