☰ Mục lục series
Tài liệu Go tiếng Việt
  1. Bắt đầu (Getting started)
    1. Tổng quan
    2. Tải về và cài đặt
    3. Bắt đầu với Go
    4. Quản lý các bản cài đặt Go
    5. Plugin trình soạn thảo và IDE
  2. Dạo quanh Go (A Tour of Go)
    1. Chào mừng
    2. Package, biến và hàm
    3. Điều khiển luồng
    4. Struct, slice và map
    5. Method và interface
    6. Generics
    7. Xử lý đồng thời
  3. Hướng dẫn (Tutorials)
    1. Danh sách hướng dẫn
    2. Tạo một Go module
    3. Gọi mã của bạn từ một module khác
    4. Trả về và xử lý lỗi
    5. Trả về lời chào ngẫu nhiên
    6. Trả về lời chào cho nhiều người
    7. Thêm một test
    8. Biên dịch và cài đặt ứng dụng
    9. Kết luận
    10. Bắt đầu với workspace nhiều module
    11. Phát triển RESTful API với Go và Gin
    12. Truy cập cơ sở dữ liệu quan hệ
    13. Bắt đầu với generics
    14. Bắt đầu với fuzzing
    15. Làm việc với JSON
    16. Tìm và sửa các dependency có lỗ hổng với govulncheck
    17. Tìm và khắc phục phụ thuộc có lỗ hổng với VS Code Go
    18. Viết ứng dụng web
  4. Viết code Go
    1. Cách viết mã Go
    2. Effective Go
    3. Doc Comment trong Go
  5. Module
    1. Phát triển và publish module
    2. Quản lý phụ thuộc
    3. Tổ chức một module Go
    4. Quản lý mã nguồn của module
    5. Cách đánh số phiên bản module
    6. Quy trình phát hành và đánh phiên bản module
    7. Publish một module
    8. Phát triển một bản cập nhật major version
    9. Tham chiếu tệp go.mod
    10. Ngừng hỗ trợ 'go get' cho việc cài đặt tệp thực thi
  6. Cơ sở dữ liệu (Database)
    1. Truy cập cơ sở dữ liệu quan hệ
    2. Mở một handle cơ sở dữ liệu
    3. Thực thi câu lệnh SQL không trả về dữ liệu
    4. Truy vấn dữ liệu
    5. Dùng prepared statement
    6. Thực thi transaction
    7. Huỷ các thao tác đang chạy
    8. Quản lý kết nối
    9. Tránh rủi ro SQL injection
  7. Chẩn đoán và hiệu năng
    1. Chẩn đoán
    2. Hướng dẫn về Go Garbage Collector
    3. Tối ưu hoá dựa trên profile
    4. Data Race Detector
    5. Gỡ lỗi mã Go bằng GDB
    6. Hỗ trợ coverage profiling cho integration test
  8. Bảo mật (Security)
    1. Tổng quan bảo mật
    2. Thực hành tốt về bảo mật cho lập trình viên Go
    3. Quản lý lỗ hổng trong Go
    4. Cơ sở dữ liệu lỗ hổng của Go
    5. Quét lỗ hổng trong IDE
    6. Chính sách CNA của Go
    7. Go Fuzzing
    8. Chi tiết kỹ thuật của Go Fuzzing
    9. Chính sách bảo mật của Go
    10. Threat Model của Go
    11. Các quyết định về bảo mật của Go
    12. Tuân thủ FIPS 140-3
  9. Công cụ và toolchain
    1. Tài liệu về các lệnh
    2. Về lệnh go
    3. Go Toolchains
    4. Telemetry của Go
    5. Cài đặt Go từ mã nguồn
    6. Cài đặt và sử dụng gccgo
  10. Tham khảo
    1. Câu hỏi thường gặp (FAQ)
    2. Go 1 và tương lai của các chương trình Go
    3. Hướng dẫn chuyển đổi sang encoding/json/v2
    4. Hướng dẫn đóng góp
    5. Đóng góp cho frontend gccgo
  11. Phụ lục
    1. Bảng thuật ngữ
Cơ sở dữ liệu (Database)

Mở một handle cơ sở dữ liệu

Bản dịch từ go.dev/doc/database/open-handle

Package database/sql đơn giản hoá việc truy cập cơ sở dữ liệu bằng cách giảm bớt nhu cầu bạn phải tự quản lý kết nối. Khác với nhiều API truy cập dữ liệu, với database/sql bạn không mở kết nối một cách tường minh, làm việc, rồi đóng kết nối. Thay vào đó, mã của bạn mở một handle cơ sở dữ liệu đại diện cho một connection pool, rồi thực thi các thao tác truy cập dữ liệu bằng handle đó, chỉ gọi hàm Close khi cần giải phóng tài nguyên, chẳng hạn tài nguyên do các dòng đã lấy về hoặc một prepared statement đang giữ.

Nói cách khác, chính handle cơ sở dữ liệu, được biểu diễn bằng sql.DB, là thứ xử lý các kết nối, mở và đóng chúng thay cho mã của bạn. Khi mã của bạn dùng handle để thực thi các thao tác cơ sở dữ liệu, những thao tác đó có quyền truy cập đồng thời vào cơ sở dữ liệu. Tìm hiểu thêm tại Quản lý kết nối.

Lưu ý: Bạn cũng có thể giữ riêng một kết nối cơ sở dữ liệu. Tìm hiểu thêm tại Dùng kết nối riêng.

Ngoài các API có sẵn trong package database/sql, cộng đồng Go đã phát triển driver cho toàn bộ những hệ quản trị cơ sở dữ liệu (DBMS) thông dụng nhất (và cả nhiều hệ ít thông dụng).

Khi mở một handle cơ sở dữ liệu, bạn thực hiện các bước tổng quát sau:

  1. Tìm một driver.

    Driver dịch các yêu cầu và phản hồi qua lại giữa mã Go của bạn và cơ sở dữ liệu. Tìm hiểu thêm tại Tìm và import driver cơ sở dữ liệu.

  2. Mở một handle cơ sở dữ liệu.

    Sau khi đã import driver, bạn có thể mở một handle cho một cơ sở dữ liệu cụ thể. Tìm hiểu thêm tại Mở một handle cơ sở dữ liệu.

  3. Xác nhận kết nối.

    Khi đã mở một handle cơ sở dữ liệu, mã của bạn có thể kiểm tra xem có kết nối khả dụng hay không. Tìm hiểu thêm tại Xác nhận kết nối.

Thông thường mã của bạn sẽ không mở hay đóng kết nối cơ sở dữ liệu một cách tường minh – việc đó do handle cơ sở dữ liệu đảm nhiệm. Tuy nhiên, mã của bạn nên giải phóng những tài nguyên đã lấy về trong quá trình làm việc, chẳng hạn một sql.Rows chứa kết quả truy vấn. Tìm hiểu thêm tại Giải phóng tài nguyên.

Tìm và import driver cơ sở dữ liệu

Bạn sẽ cần một driver cơ sở dữ liệu hỗ trợ DBMS mà bạn đang dùng. Để tìm driver cho cơ sở dữ liệu của bạn, xem SQLDrivers.

Để mã của bạn dùng được driver, bạn import nó giống như khi import một package Go khác. Đây là một ví dụ:

import "github.com/go-sql-driver/mysql"

Lưu ý rằng nếu bạn không gọi trực tiếp hàm nào từ package driver –- chẳng hạn khi nó được package sql dùng một cách ngầm định – bạn sẽ cần dùng blank import, tức là thêm tiền tố dấu gạch dưới vào import path:

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

Lưu ý: Theo thực hành tốt, hãy tránh dùng API riêng của driver cơ sở dữ liệu cho các thao tác cơ sở dữ liệu. Thay vào đó, hãy dùng các hàm trong package database/sql. Điều này giúp mã của bạn ít bị ràng buộc chặt với DBMS, nhờ đó dễ chuyển sang một DBMS khác hơn khi cần.

Mở một handle cơ sở dữ liệu

Handle cơ sở dữ liệu sql.DB cho phép đọc và ghi vào một cơ sở dữ liệu, riêng lẻ hoặc trong một transaction.

Bạn có thể lấy một handle cơ sở dữ liệu bằng cách gọi sql.Open (nhận một chuỗi kết nối) hoặc sql.OpenDB (nhận một driver.Connector). Cả hai đều trả về một con trỏ tới sql.DB.

Lưu ý: Hãy nhớ không để thông tin đăng nhập cơ sở dữ liệu nằm trong mã nguồn Go. Tìm hiểu thêm tại Lưu trữ thông tin đăng nhập cơ sở dữ liệu.

Mở bằng chuỗi kết nối

Dùng hàm sql.Open khi bạn muốn kết nối bằng một chuỗi kết nối. Định dạng của chuỗi này khác nhau tuỳ theo driver bạn đang dùng.

Đây là một ví dụ cho MySQL:

db, err = sql.Open("mysql", "username:password@tcp(127.0.0.1:3306)/jazzrecords")
if err != nil {
	log.Fatal(err)
}

Tuy nhiên, bạn có lẽ sẽ thấy rằng việc ghi nhận các thuộc tính kết nối theo cách có cấu trúc hơn sẽ cho ra mã dễ đọc hơn. Chi tiết sẽ khác nhau tuỳ driver.

Ví dụ, bạn có thể thay ví dụ ở trên bằng đoạn sau, nó dùng Config của driver MySQL để chỉ định các thuộc tính và dùng hàm FormatDSN để dựng nên chuỗi kết nối.

// Specify connection properties.
cfg := mysql.NewConfig()
cfg.User = username
cfg.Passwd = password
cfg.Net = "tcp"
cfg.Addr = "127.0.0.1:3306"
cfg.DBName = "jazzrecords"

// Get a database handle.
db, err = sql.Open("mysql", cfg.FormatDSN())
if err != nil {
	log.Fatal(err)
}

Mở bằng một Connector

Dùng hàm sql.OpenDB khi bạn muốn tận dụng các tính năng kết nối riêng của driver mà chuỗi kết nối không cung cấp. Mỗi driver hỗ trợ tập thuộc tính kết nối của riêng nó, thường cung cấp những cách tuỳ chỉnh yêu cầu kết nối đặc thù cho DBMS.

Chuyển ví dụ sql.Open ở trên sang dùng sql.OpenDB, bạn có thể tạo một handle bằng đoạn mã như sau:

// Specify connection properties.
cfg := mysql.NewConfig()
cfg.User = username
cfg.Passwd = password
cfg.Net = "tcp"
cfg.Addr = "127.0.0.1:3306"
cfg.DBName = "jazzrecords"

// Get a driver-specific connector.
connector, err := mysql.NewConnector(&cfg)
if err != nil {
	log.Fatal(err)
}

// Get a database handle.
db = sql.OpenDB(connector)

Xử lý lỗi

Mã của bạn nên kiểm tra lỗi phát sinh khi tạo handle, chẳng hạn với sql.Open. Đó sẽ không phải lỗi kết nối. Thay vào đó, bạn sẽ nhận được lỗi nếu sql.Open không khởi tạo được handle. Điều này có thể xảy ra, ví dụ, nếu nó không phân tích được DSN mà bạn chỉ định.

Xác nhận kết nối

Khi bạn mở một handle cơ sở dữ liệu, package sql có thể không tự tạo ngay một kết nối cơ sở dữ liệu mới. Thay vào đó, nó có thể tạo kết nối khi mã của bạn cần đến. Nếu bạn chưa dùng cơ sở dữ liệu ngay và muốn xác nhận rằng có thể thiết lập được kết nối, hãy gọi Ping hoặc PingContext.

Mã trong ví dụ sau ping cơ sở dữ liệu để xác nhận kết nối.

db, err = sql.Open("mysql", connString)

// Confirm a successful connection.
if err := db.Ping(); err != nil {
	log.Fatal(err)
}

Lưu trữ thông tin đăng nhập cơ sở dữ liệu

Hãy tránh lưu thông tin đăng nhập cơ sở dữ liệu trong mã nguồn Go, điều đó có thể để lộ nội dung cơ sở dữ liệu của bạn cho người khác. Thay vào đó, hãy tìm cách lưu chúng ở một nơi bên ngoài mã nhưng mã vẫn truy cập được. Ví dụ, hãy cân nhắc một ứng dụng giữ bí mật (secret keeper) chuyên lưu thông tin đăng nhập và cung cấp một API để mã của bạn lấy về thông tin đăng nhập nhằm xác thực với DBMS.

Một cách tiếp cận phổ biến là lưu các bí mật vào môi trường trước khi chương trình khởi chạy, có thể nạp từ một trình quản lý bí mật, rồi chương trình Go của bạn có thể đọc chúng bằng os.Getenv:

username := os.Getenv("DB_USER")
password := os.Getenv("DB_PASS")

Cách này cũng cho phép bạn tự thiết lập các biến môi trường để kiểm thử cục bộ.

Giải phóng tài nguyên

Dù bạn không quản lý hay đóng kết nối một cách tường minh với package database/sql, mã của bạn vẫn nên giải phóng những tài nguyên đã lấy về khi không còn cần đến chúng. Đó có thể là tài nguyên do một sql.Rows đại diện cho dữ liệu trả về từ một truy vấn, hoặc một sql.Stmt đại diện cho một prepared statement đang giữ.

Thông thường, bạn đóng tài nguyên bằng cách defer một lời gọi hàm Close để tài nguyên được giải phóng trước khi hàm bao ngoài kết thúc.

Mã trong ví dụ sau defer Close để giải phóng tài nguyên do sql.Rows đang giữ.

rows, err := db.Query("SELECT * FROM album WHERE artist = ?", artist)
if err != nil {
	log.Fatal(err)
}
defer rows.Close()

// Loop through returned rows.

Nội dung gốc: Go Documentation © The Go Authors, giấy phép CC BY 3.0. Bản dịch và ghi chú: Lê Văn Dũng.