Chuyển tới nội dung chính

Database trong Go - Cách lưu trữ dữ liệu thông minh! 💾

Chào mừng bạn đến với bài học về Database trong Go! Trong bài học này, chúng ta sẽ tìm hiểu cách làm việc với cơ sở dữ liệu một cách hiệu quả và an toàn.

Database là gì? 🤔

Database giống như một kho lưu trữ thông tin - nó giúp chúng ta:

  • Lưu trữ dữ liệu một cách có tổ chức
  • Truy xuất thông tin nhanh chóng
  • Đảm bảo tính nhất quán của dữ liệu

💡 Ví dụ thực tế:

  • Giống như một thư viện có sách được phân loại
  • Mỗi cuốn sách có vị trí riêng
  • Dễ dàng tìm và mượn sách

Kết nối Database 🔌

1. Thiết lập kết nối

func connectDB() (*sql.DB, error) {
// Tạo kết nối đến MySQL
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
if err != nil {
return nil, fmt.Errorf("không thể kết nối: %w", err)
}

// Kiểm tra kết nối
if err := db.Ping(); err != nil {
return nil, fmt.Errorf("không thể ping database: %w", err)
}

return db, nil
}

💡 Giải thích:

  • sql.Open: Tạo kết nối mới
  • db.Ping: Kiểm tra kết nối
  • Luôn xử lý lỗi cẩn thận

2. Đóng kết nối

func main() {
// Kết nối database
db, err := connectDB()
if err != nil {
log.Fatal(err)
}
// Đảm bảo đóng kết nối khi kết thúc
defer db.Close()
}

💡 Giải thích:

  • defer db.Close(): Đóng kết nối khi kết thúc
  • Tránh rò rỉ tài nguyên
  • Đảm bảo an toàn

Truy vấn SQL 📝

1. Truy vấn đơn giản

func getUsers(db *sql.DB) ([]User, error) {
// Thực hiện truy vấn
rows, err := db.Query("SELECT id, name, email FROM users")
if err != nil {
return nil, fmt.Errorf("lỗi truy vấn: %w", err)
}
defer rows.Close() // Đóng kết quả khi kết thúc

var users []User
// Duyệt qua từng dòng kết quả
for rows.Next() {
var user User
if err := rows.Scan(&user.ID, &user.Name, &user.Email); err != nil {
return nil, fmt.Errorf("lỗi đọc dữ liệu: %w", err)
}
users = append(users, user)
}

return users, nil
}

💡 Giải thích:

  • db.Query: Thực hiện truy vấn
  • rows.Next(): Di chuyển đến dòng tiếp theo
  • rows.Scan: Đọc dữ liệu từ dòng hiện tại

2. Prepared Statements

func getUser(db *sql.DB, id string) (*User, error) {
// Chuẩn bị câu truy vấn
stmt, err := db.Prepare("SELECT id, name, email FROM users WHERE id = ?")
if err != nil {
return nil, fmt.Errorf("lỗi chuẩn bị truy vấn: %w", err)
}
defer stmt.Close()

// Thực hiện truy vấn với tham số
var user User
err = stmt.QueryRow(id).Scan(&user.ID, &user.Name, &user.Email)
if err != nil {
return nil, fmt.Errorf("lỗi thực thi truy vấn: %w", err)
}

return &user, nil
}

💡 Giải thích:

  • db.Prepare: Chuẩn bị câu truy vấn
  • stmt.QueryRow: Thực thi với tham số
  • An toàn hơn với SQL injection

Giao dịch (Transactions) 💰

1. Giao dịch cơ bản

func transferMoney(db *sql.DB, from, to string, amount float64) error {
// Bắt đầu giao dịch
tx, err := db.Begin()
if err != nil {
return fmt.Errorf("lỗi bắt đầu giao dịch: %w", err)
}
defer tx.Rollback() // Hoàn tác nếu có lỗi

// Trừ tiền từ tài khoản gửi
_, err = tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, from)
if err != nil {
return fmt.Errorf("lỗi trừ tiền: %w", err)
}

// Cộng tiền vào tài khoản nhận
_, err = tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, to)
if err != nil {
return fmt.Errorf("lỗi cộng tiền: %w", err)
}

// Xác nhận giao dịch
return tx.Commit()
}

💡 Giải thích:

  • db.Begin(): Bắt đầu giao dịch
  • tx.Commit(): Xác nhận giao dịch
  • tx.Rollback(): Hoàn tác khi có lỗi

2. Giao dịch với Context

func transferMoneyWithContext(ctx context.Context, db *sql.DB, from, to string, amount float64) error {
// Bắt đầu giao dịch với context
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return fmt.Errorf("lỗi bắt đầu giao dịch: %w", err)
}
defer tx.Rollback()

// Thực hiện các thao tác chuyển tiền

return tx.Commit()
}

💡 Giải thích:

  • Sử dụng context để kiểm soát thời gian
  • Có thể hủy giao dịch khi cần
  • Phù hợp cho các thao tác dài

ORM với GORM 🎯

1. Khởi tạo GORM

type User struct {
ID uint
Name string
Email string
}

func initGORM() (*gorm.DB, error) {
// Kết nối database với GORM
db, err := gorm.Open("mysql", "user:password@tcp(localhost:3306)/dbname?charset=utf8&parseTime=True&loc=Local")
if err != nil {
return nil, fmt.Errorf("lỗi kết nối: %w", err)
}

return db, nil
}

💡 Giải thích:

  • GORM giúp làm việc với database dễ dàng hơn
  • Tự động xử lý nhiều thao tác phức tạp
  • Hỗ trợ nhiều tính năng tiện ích

2. CRUD với GORM

// Tạo user mới
func createUser(db *gorm.DB, user *User) error {
return db.Create(user).Error
}

// Lấy user theo ID
func getUser(db *gorm.DB, id uint) (*User, error) {
var user User
err := db.First(&user, id).Error
return &user, err
}

// Cập nhật user
func updateUser(db *gorm.DB, user *User) error {
return db.Save(user).Error
}

// Xóa user
func deleteUser(db *gorm.DB, id uint) error {
return db.Delete(&User{}, id).Error
}

💡 Giải thích:

  • Create: Tạo bản ghi mới
  • First: Lấy bản ghi đầu tiên
  • Save: Cập nhật bản ghi
  • Delete: Xóa bản ghi

Quản lý Kết nối 🔄

1. Cấu hình Pool

func configurePool(db *sql.DB) {
// Giới hạn số kết nối tối đa
db.SetMaxOpenConns(25)
// Giới hạn số kết nối không sử dụng
db.SetMaxIdleConns(25)
// Thời gian tối đa một kết nối có thể tồn tại
db.SetConnMaxLifetime(5 * time.Minute)
}

💡 Giải thích:

  • Quản lý số lượng kết nối
  • Tối ưu hiệu suất
  • Tránh quá tải

2. Kiểm tra Sức khỏe

func checkDBHealth(db *sql.DB) error {
// Tạo context với timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

// Kiểm tra kết nối
return db.PingContext(ctx)
}

💡 Giải thích:

  • Kiểm tra kết nối còn hoạt động
  • Phát hiện sớm vấn đề
  • Đảm bảo độ tin cậy

Migration (Di chuyển) 🚀

1. SQL Migration

func runMigrations(db *sql.DB) error {
migrations := []string{
`CREATE TABLE IF NOT EXISTS users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL
)`,
// Thêm các migration khác
}

for _, migration := range migrations {
_, err := db.Exec(migration)
if err != nil {
return fmt.Errorf("lỗi migration: %w", err)
}
}

return nil
}

💡 Giải thích:

  • Tạo và cập nhật cấu trúc database
  • Đảm bảo tính nhất quán
  • Dễ dàng quản lý thay đổi

2. GORM Migration

func runGORMMigrations(db *gorm.DB) error {
return db.AutoMigrate(
&User{},
&Post{},
&Comment{},
).Error
}

💡 Giải thích:

  • Tự động tạo cấu trúc từ model
  • Đơn giản và dễ sử dụng
  • Giảm thiểu lỗi

Tối ưu Truy vấn ⚡

1. Tạo Index

func createIndexes(db *sql.DB) error {
_, err := db.Exec(`
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_posts_user_id ON posts(user_id);
`)
return err
}

💡 Giải thích:

  • Tăng tốc độ tìm kiếm
  • Giảm tải cho database
  • Cải thiện hiệu suất

2. Phân tích Truy vấn

func explainQuery(db *sql.DB, query string) error {
rows, err := db.Query("EXPLAIN " + query)
if err != nil {
return err
}
defer rows.Close()

// Phân tích kế hoạch thực thi
return nil
}

💡 Giải thích:

  • Hiểu cách database thực thi truy vấn
  • Tìm điểm cần tối ưu
  • Cải thiện hiệu suất

Xử lý Lỗi 🚨

1. Lỗi Database

func handleDBError(err error) error {
if err == sql.ErrNoRows {
return errors.New("không tìm thấy bản ghi")
}
if err == sql.ErrConnDone {
return errors.New("kết nối database đã đóng")
}
return err
}

💡 Giải thích:

  • Xử lý các lỗi phổ biến
  • Thông báo lỗi rõ ràng
  • Dễ dàng debug

2. Thử lại khi Lỗi

func withRetry(db *sql.DB, fn func() error) error {
var err error
for i := 0; i < 3; i++ {
err = fn()
if err == nil {
return nil
}
// Tăng thời gian chờ sau mỗi lần thử
time.Sleep(time.Second * time.Duration(i+1))
}
return err
}

💡 Giải thích:

  • Tự động thử lại khi lỗi
  • Tăng độ tin cậy
  • Xử lý lỗi tạm thời

Best Practices (Cách sử dụng tốt nhất) ✅

  1. Kết nối an toàn

    // ✅ Đúng
    db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
    if err != nil {
    return nil, fmt.Errorf("lỗi kết nối: %w", err)
    }

    // ❌ Sai
    db, _ := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
  2. Sử dụng Prepared Statements

    // ✅ Đúng
    stmt, err := db.Prepare("SELECT * FROM users WHERE id = ?")

    // ❌ Sai
    query := fmt.Sprintf("SELECT * FROM users WHERE id = %d", id)
  3. Xử lý giao dịch

    // ✅ Đúng
    tx, err := db.Begin()
    defer tx.Rollback()

    // ❌ Sai
    tx, err := db.Begin()
    // Không có defer tx.Rollback()
  4. Đóng kết nối

    // ✅ Đúng
    defer db.Close()

    // ❌ Sai
    // Không đóng kết nối

Tiếp theo 🎯

Trong các bài học tiếp theo, chúng ta sẽ:

  • Tìm hiểu về caching
  • Học cách tối ưu hiệu suất database
  • Khám phá các công cụ quản lý database
  • Thực hành với các dự án thực tế

💡 Lời khuyên: Hãy nghĩ về database như một kho lưu trữ thông tin. Khi tổ chức tốt, việc tìm và sử dụng thông tin sẽ trở nên dễ dàng hơn!