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:
-
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.
-
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.
-
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.