Database
Tôi sẽ giải thích chi tiết cách làm việc với database trong Golang, bao gồm kết nối bằng database/sql
, sử dụng ORM (GORM), quản lý transaction (Begin
, Commit
, Rollback
), và tối ưu hóa truy vấn với indexing. Mỗi phần sẽ có ví dụ thực tế và best practices.
1. Kết nối Database với database/sql
và ORM (GORM)
database/sql
và ORM (GORM)database/sql
- Kết nối cơ bản
Package
database/sql
là cách chuẩn để làm việc với SQL trong Go, hỗ trợ nhiều driver (MySQL, PostgreSQL, SQLite, v.v.).Cần import driver tương ứng (ví dụ:
github.com/go-sql-driver/mysql
).
Ví dụ với MySQL:
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql" // Driver MySQL
)
func main() {
// DSN: "user:password@tcp(host:port)/dbname"
dsn := "root:password@tcp(127.0.0.1:3306)/testdb?parseTime=true"
db, err := sql.Open("mysql", dsn)
if err != nil {
panic(err)
}
defer db.Close()
// Kiểm tra kết nối
err = db.Ping()
if err != nil {
panic(err)
}
fmt.Println("Connected to database!")
// Ví dụ truy vấn
rows, err := db.Query("SELECT id, name FROM users")
if err != nil {
panic(err)
}
defer rows.Close()
for rows.Next() {
var id int
var name string
if err := rows.Scan(&id, &name); err != nil {
panic(err)
}
fmt.Printf("ID: %d, Name: %s\n", id, name)
}
}
Best Practices với database/sql
:
Connection Pooling:
sql.Open
không thực sự mở kết nối mà tạo một pool. Dùngdb.SetMaxOpenConns(n)
để giới hạn số kết nối.Error Handling: Luôn kiểm tra lỗi sau mỗi thao tác (
Ping
,Query
,Exec
).Prepared Statements: Dùng
db.Prepare
để tránh SQL injection và tăng hiệu suất.
GORM - ORM trong Golang
GORM (
gorm.io/gorm
) là một ORM phổ biến, giúp đơn giản hóa việc ánh xạ struct Go với bảng database.
Ví dụ với GORM (MySQL):
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"type:varchar(100)"`
Email string `gorm:"unique"`
}
func main() {
dsn := "root:password@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
// Auto-migrate để tạo bảng
db.AutoMigrate(&User{})
// Thêm dữ liệu
user := User{Name: "John", Email: "john@example.com"}
db.Create(&user)
// Truy vấn
var foundUser User
db.First(&foundUser, "email = ?", "john@example.com")
fmt.Printf("Found: %+v\n", foundUser)
}
Best Practices với GORM:
Tag: Dùng tag
gorm:"..."
để định nghĩa ràng buộc (primary key, unique, v.v.).Preloading: Dùng
db.Preload("Relation")
để tải quan hệ (associations).Debug: Dùng
db.Debug()
để log SQL trong quá trình phát triển.
So sánh:
database/sql
: Linh hoạt, nhẹ, nhưng cần viết nhiều code thủ công.GORM: Tiện lợi, phù hợp với ứng dụng CRUD nhanh, nhưng nặng hơn và có thể khó tối ưu truy vấn phức tạp.
2. Transaction (Begin, Commit, Rollback)
Transaction với database/sql
Transaction đảm bảo tính toàn vẹn dữ liệu (atomicity) khi thực hiện nhiều thao tác.
Ví dụ:
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
func transferMoney(db *sql.DB, fromID, toID, amount int) error {
tx, err := db.Begin()
if err != nil {
return err
}
// Rollback nếu có lỗi
defer func() {
if err != nil {
tx.Rollback()
return
}
err = tx.Commit()
}()
// Trừ tiền từ tài khoản nguồn
_, err = tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, fromID)
if err != nil {
return err
}
// Cộng tiền vào tài khoản đích
_, err = tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, toID)
if err != nil {
return err
}
return nil
}
func main() {
db, err := sql.Open("mysql", "root:password@tcp(127.0.0.1:3306)/testdb")
if err != nil {
panic(err)
}
defer db.Close()
err = transferMoney(db, 1, 2, 100)
if err != nil {
fmt.Println("Transfer failed:", err)
} else {
fmt.Println("Transfer successful!")
}
}
Transaction với GORM
GORM hỗ trợ transaction qua
db.Transaction()
hoặcdb.Begin()
.
Ví dụ:
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type Account struct {
ID uint `gorm:"primaryKey"`
Balance int
}
func transferMoneyGORM(db *gorm.DB, fromID, toID, amount int) error {
return db.Transaction(func(tx *gorm.DB) error {
var fromAcc Account
if err := tx.First(&fromAcc, fromID).Error; err != nil {
return err
}
if fromAcc.Balance < amount {
return fmt.Errorf("insufficient balance")
}
// Trừ tiền
if err := tx.Model(&Account{}).Where("id = ?", fromID).Update("balance", fromAcc.Balance-amount).Error; err != nil {
return err
}
// Cộng tiền
if err := tx.Model(&Account{}).Where("id = ?", toID).Update("balance", gorm.Expr("balance + ?", amount)).Error; err != nil {
return err
}
return nil
})
}
func main() {
db, err := gorm.Open(mysql.Open("root:password@tcp(127.0.0.1:3306)/testdb"), &gorm.Config{})
if err != nil {
panic(err)
}
db.AutoMigrate(&Account{})
err = transferMoneyGORM(db, 1, 2, 100)
if err != nil {
fmt.Println("Transfer failed:", err)
} else {
fmt.Println("Transfer successful!")
}
}
Best Practices:
Defer Rollback: Với
database/sql
, dùngdefer
để đảm bảo rollback nếu lỗi.Kiểm tra điều kiện: Validate trước khi thực hiện transaction (ví dụ: đủ số dư).
Giới hạn phạm vi: Chỉ dùng transaction cho các thao tác cần atomicity.
3. Indexing & Query Optimization
Indexing
Index tăng tốc độ truy vấn bằng cách giảm số hàng cần quét.
Các loại index:
Primary Key: Tự động có index.
Unique Index: Đảm bảo giá trị duy nhất (ví dụ: email).
Composite Index: Kết hợp nhiều cột (ví dụ:
name
+created_at
).
Tạo Index với SQL:
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at);
Với GORM:
type User struct {
ID uint `gorm:"primaryKey"`
Name string
Email string `gorm:"index:idx_email,unique"` // Index trên email
}
type Order struct {
ID uint `gorm:"primaryKey"`
UserID uint
CreatedAt time.Time `gorm:"index:idx_user_date,composite:user_id"` // Composite index
}
Query Optimization
Giảm quét toàn bảng: Dùng
WHERE
với cột có index.**Tránh SELECT * **: Chỉ lấy cột cần thiết.
Sử dụng EXPLAIN: Phân tích kế hoạch truy vấn.
Ví dụ tối ưu với database/sql
:
// Truy vấn không tối ưu
rows, err := db.Query("SELECT * FROM users WHERE name LIKE '%john%'")
// Tối ưu: Dùng index trên email
rows, err := db.Query("SELECT id, name FROM users WHERE email = ?", "john@example.com")
Với GORM:
// Không tối ưu
var users []User
db.Where("name LIKE ?", "%john%").Find(&users)
// Tối ưu
var user User
db.Select("id", "name").Where("email = ?", "john@example.com").First(&user)
Phân tích với EXPLAIN:
Chạy
EXPLAIN SELECT ...
trong MySQL/PostgreSQL để xem kế hoạch truy vấn:
EXPLAIN SELECT id, name FROM users WHERE email = 'john@example.com';
Kiểm tra
key
(index được dùng) vàrows
(số hàng quét).
Best Practices:
Index cẩn thận: Quá nhiều index làm chậm
INSERT
/UPDATE
.Profile truy vấn: Dùng
db.Debug()
(GORM) hoặcEXPLAIN
để tìm bottleneck.Batch Processing: Với dữ liệu lớn, chia nhỏ truy vấn (ví dụ:
LIMIT 1000 OFFSET 0
).
Tổng kết
database/sql
: Linh hoạt, nhẹ, phù hợp với truy vấn phức tạp.GORM: Tiện lợi, nhanh cho CRUD, nhưng cần cẩn thận với hiệu suất.
Transaction: Đảm bảo atomicity với
Begin/Commit/Rollback
.Indexing & Optimization: Tăng tốc truy vấn bằng index và tối ưu SQL.
Last updated