Bạn có thể thực thi các transaction cơ sở dữ liệu bằng sql.Tx,, kiểu đại diện cho một transaction. Ngoài các method Commit và Rollback thể hiện ngữ nghĩa riêng của transaction, sql.Tx còn có đầy đủ các method mà bạn dùng để thực hiện những thao tác cơ sở dữ liệu thông thường. Để lấy sql.Tx, bạn gọi DB.Begin hoặc DB.BeginTx.
Một transaction cơ sở dữ liệu gom nhiều thao tác lại thành một phần của một mục tiêu lớn hơn. Tất cả các thao tác đều phải thành công, hoặc không thao tác nào được thực hiện, và trong cả hai trường hợp tính toàn vẹn của dữ liệu đều được giữ nguyên. Thông thường, quy trình của một transaction gồm:
- Bắt đầu transaction.
- Thực hiện một loạt thao tác cơ sở dữ liệu.
- Nếu không có lỗi xảy ra, commit transaction để áp dụng các thay đổi lên cơ sở dữ liệu.
- Nếu có lỗi xảy ra, rollback transaction để giữ nguyên cơ sở dữ liệu không thay đổi.
Package sql cung cấp các method để bắt đầu và kết thúc một transaction, cũng như các method để thực hiện những thao tác cơ sở dữ liệu ở giữa. Các method này tương ứng với bốn bước trong quy trình nêu trên.
-
Bắt đầu một transaction.
DB.BeginhoặcDB.BeginTxbắt đầu một transaction cơ sở dữ liệu mới, trả về mộtsql.Txđại diện cho nó. -
Thực hiện các thao tác cơ sở dữ liệu.
Với một
sql.Tx, bạn có thể truy vấn hoặc cập nhật cơ sở dữ liệu qua một loạt thao tác dùng chung một kết nối duy nhất. Để hỗ trợ điều này,Txxuất ra những method sau:-
ExecvàExecContextđể thay đổi cơ sở dữ liệu bằng các câu lệnh SQL nhưINSERT,UPDATE, vàDELETE.Tìm hiểu thêm tại Thực thi câu lệnh SQL không trả về dữ liệu.
-
Query,QueryContext,QueryRow, vàQueryRowContextcho những thao tác trả về các row.Tìm hiểu thêm tại Truy vấn dữ liệu.
-
Prepare,PrepareContext,Stmt, vàStmtContextđể định nghĩa trước các prepared statement.Tìm hiểu thêm tại Dùng prepared statement.
-
-
Kết thúc transaction bằng một trong hai cách sau:
-
Commit transaction bằng
Tx.Commit.Nếu
Committhành công (trả về lỗinil), thì toàn bộ kết quả truy vấn được xác nhận là hợp lệ và mọi cập nhật đã thực thi đều được áp dụng lên cơ sở dữ liệu như một thay đổi nguyên tử duy nhất. NếuCommitthất bại, thì mọi kết quả từQueryvàExectrênTxđó nên bị xem là không hợp lệ và loại bỏ. -
Rollback transaction bằng
Tx.Rollback.Ngay cả khi
Tx.Rollbackthất bại, transaction cũng sẽ không còn hợp lệ nữa, và nó cũng chưa từng được commit vào cơ sở dữ liệu.
-
Thực hành tốt
Hãy làm theo những thực hành tốt dưới đây để xoay xở tốt hơn với phần ngữ nghĩa phức tạp và việc quản lý kết nối mà transaction đôi khi đòi hỏi.
- Dùng các API được mô tả trong phần này để quản lý transaction. Không dùng trực tiếp các câu lệnh SQL liên quan đến transaction như
BEGINvàCOMMIT— làm vậy có thể khiến cơ sở dữ liệu của bạn rơi vào trạng thái không lường trước được, nhất là trong các chương trình chạy đồng thời. - Khi đang dùng một transaction, hãy cẩn thận đừng gọi trực tiếp thêm các method
sql.DBngoài transaction, vì chúng sẽ được thực thi bên ngoài transaction, khiến mã của bạn thấy một trạng thái cơ sở dữ liệu không nhất quán, hoặc thậm chí gây ra deadlock.
Ví dụ
Mã trong ví dụ dưới đây dùng một transaction để tạo một đơn hàng mới của khách cho một album. Trong quá trình đó, mã sẽ:
- Bắt đầu một transaction.
- Defer việc rollback transaction. Nếu transaction thành công, nó sẽ được commit trước khi hàm kết thúc, khiến lệnh gọi rollback đã defer trở thành vô hiệu. Nếu transaction thất bại thì nó sẽ không được commit, nghĩa là rollback sẽ được gọi khi hàm kết thúc.
- Xác nhận rằng tồn kho đủ cho album mà khách đang đặt.
- Nếu đủ, cập nhật số lượng tồn kho, giảm đi theo số album đã đặt.
- Tạo một đơn hàng mới và lấy ID sinh ra của đơn hàng mới cho client.
- Commit transaction và trả về ID.
Ví dụ này dùng các method của Tx nhận đối số context.Context. Nhờ đó, việc thực thi của hàm – bao gồm cả các thao tác cơ sở dữ liệu – có thể bị huỷ nếu nó chạy quá lâu hoặc kết nối của client bị đóng. Tìm hiểu thêm tại Huỷ các thao tác đang chạy.
// CreateOrder creates an order for an album and returns the new order ID.
func CreateOrder(ctx context.Context, albumID, quantity, custID int) (orderID int64, err error) {
// Create a helper function for preparing failure results.
fail := func(err error) (int64, error) {
return 0, fmt.Errorf("CreateOrder: %v", err)
}
// Get a Tx for making transaction requests.
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return fail(err)
}
// Defer a rollback in case anything fails.
defer tx.Rollback()
// Confirm that album inventory is enough for the order.
var enough bool
if err = tx.QueryRowContext(ctx, "SELECT (quantity >= ?) from album where id = ?",
quantity, albumID).Scan(&enough); err != nil {
if err == sql.ErrNoRows {
return fail(fmt.Errorf("no such album"))
}
return fail(err)
}
if !enough {
return fail(fmt.Errorf("not enough inventory"))
}
// Update the album inventory to remove the quantity in the order.
_, err = tx.ExecContext(ctx, "UPDATE album SET quantity = quantity - ? WHERE id = ?",
quantity, albumID)
if err != nil {
return fail(err)
}
// Create a new row in the album_order table.
result, err := tx.ExecContext(ctx, "INSERT INTO album_order (album_id, cust_id, quantity, date) VALUES (?, ?, ?, ?)",
albumID, custID, quantity, time.Now())
if err != nil {
return fail(err)
}
// Get the ID of the order item just created.
orderID, err = result.LastInsertId()
if err != nil {
return fail(err)
}
// Commit the transaction.
if err = tx.Commit(); err != nil {
return fail(err)
}
// Return the order ID.
return orderID, nil
}