Transaction là gì?

Transaction là một nhóm các lệnh SQL được gói gọn trong một đơn vị không thể phân chia. Transaction thành công nếu nó committed (được cam kết – trong bối cảnh các giao dịch nên có thể hiểu là cam kết hoặc có thể hiểu là hoàn thành), thất bại nếu nó bị huỷ bỏ và lúc này nó sẽ ở trạng thái gọi là rolled back (quay lui) tức là sẽ trở lại trạng thái bắt đầu của transaction . Bốn tính chất cơ bản của transaction là:

  • Tính nguyên tử (atomicity): transaction không thể được phân chia thành những đơn vị nhỏ hơn và phải được hoàn thành trọn vẹn, nghĩa là hoàn thành tất cả các lệnh bên trong transaction hay không làm gì cả. Mọi sự thực thi nửa vời đều phát sinh lỗi.
  • Tính nhất quán (consistency): một transaction phải thao tác trên cách nhìn nhất quán của dữ liệu và dữ liệu phải được đặt trong trạng thái nhất quán. Một transaction phải không được nhìn thấy bởi các transaction khác cho đến khi nó được cam kết.
  • Tính độc lập (isolation): một transaction phải độc lập với các transaction khác, nghĩa là nó không ảnh hưởng đến transaction khác và các transaction khác cũng không ảnh hưởng đến nó.
  • Tính bền vững (durability): khi một transaction được cam kết, nó sẽ được duy trì ngay cả khi mất nguồn hay hệ thống khác thất bại.

Mô hình tương tranh (concurrency models) và cơ chế khoá (locking) cơ sở dữ liệu

Trong mô hình tương tranh (concurrency model), các transaction có thể truy cập dữ liệu đồng thời và sẽ dẫn tới xung đột. Để xử lý vấn đề xung đột, có hai giải pháp là sử dụng cơ chế khoá (locking) và không dùng cơ chế khoá:

  • Dùng cơ chế khoá: khi một transaction truy cập dữ liệu mà một transaction khác đang “chiếm hữu” thì dữ liệu sẽ bị khoá cho đến khi transaction “chiếm hữu” được cam kết hay rolled back – lúc này khoá được giải phóng. Dùng cơ chế khoá có thể ảnh hưởng tới khả năng thực thi của ứng dụng nếu thời gian chờ khoá được giải phóng kéo dài do đó mô hình dùng cơ chế này còn được gọi là mô hình tương tranh bi quan (pessimistic concurrency model).
  • Không dùng cơ chế khoá: trong mô hình này dữ liệu không bị khoá khi được đọc, thay vì vậy, dữ liệu sẽ được kiểm tra có thay đổi hay không từ khi nó được đọc. Nếu dữ liệu thay đổi, một ngoại lệ sẽ phát sinh và ứng dụng sẽ áp dụng các luật mức business (business logic) để phục hồi. Mô hình áp dụng cơ chế không dùng khoá gọi mà mô hình tương tranh lạc quan (optimistic concurrency model).

Các mức độ độc lập của transaction

Độc lập đầy đủ (complete isolation) có thể rất tốt nhưng nó sẽ đi kèm với chi phí cao bởi vì độc lập đầy đủ có nghĩa là dữ liệu phải được khoá khi nó được đọc hay ghi bởi một transaction. Tuỳ theo ứng dụng mà có thể không cần độc lập đầy đủ để tăng khả năng thực thi hay mở rộng của ứng dụng. Mức độ độc lập của transaction có thể gây ra một số ảnh hưởng sau:

  • Dirty read (đọc bẩn): đọc dữ liệu bị thay đổi bởi một transaction chưa được cam kết.
  • Nonrepeatable read (đọc không lặp lại): kết quả các lần đọc dữ liệu cùng một hàng bởi một transaction là khác nhau bởi vì một transaction khác thay đổi giữa các lần đọc.
  • Phantom read (đọc bóng ma): khi một transaction đọc một hàng sẽ bị xoá bởi một transaction khác hay khi đọc lần thứ hai tìm thấy một hàng mới được chèn bởi một transaction khác.

Các mức độ độc lập trong SQL Server và các ảnh hưởng có thể được liệt kê như bảng dưới đây:

  • Read Uncommitted: các truy vấn trong một transaction bị ảnh hưởng bởi các thay đổi chưa được cam kết trong một transaction khác. Khoá không được dùng ở mức này.
  • Read Committed with Locks: thiết lập mặc định trong SQL Server. Các cập nhật được cam kết bởi một transaction có thể được nhìn thấy bởi các transaction khác. Các truy vấn hay các kết hợp (aggregations) có thời gian thực thi lâu sẽ không được yêu cầu.
  • Read Committed with Snapshots: chỉ các cập nhật được cam kết mới được thấy bởi các transaction khác và các truy vấn hay các kết hợp (aggregations) có thời gian thực thi lâu được yêu cầu. Khoá không được yêu cầu và cơ chế row versioning được sử dụng để giám sát sự thay đổi phiên bản của hàng. Lưu trữ phiên bản hàng sử dụng ở mức này có thể gây tốn kém chi phí (tài nguyên) nhưng nó sẽ tăng thông lượng (throughput) bằng cách giảm nội dung dữ liệu bị khoá.
  • Repeatable Read: Với một transaction, việc đọc dữ liệu là nhất quán; các transaction khác không thể gây ảnh hưởng đến kết quả truy vấn của chúng ta cho đến khi transaction hoàn thành và khoá được giải phóng. Mức này được ưu tiên sử dụng khi chúng ta đọc dữ liệu nhằm mục đích chỉnh sửa trong cùng một transaction.
  • Snapshot: được dùng khi các truy vấn thời gian dài và các transaction nhiều lệnh được yêu cầu nhưng không có kế hoạch cập nhật dữ liệu. Khoá không được yêu cầu cho quá trình đọc dữ liệu bởi vì các thay đổi không thể được nhìn thấy cho đến khi snapshot hoàn thành và các transaction cam kết.
  • Serializable: đặt một khoá phạm vi (a range lock) – là khoá nhiều hàng (multi-row lock)- trên tập hàng đầy đủ được truy cập, ngăn chặn người dùng khác cập nhật hay chèn các hàng mới vào tập dữ liệu cho đến khi transaction hoàn thành. Đây là mức nghiêm ngặt nhất vì nó sử dụng rất nhiều khoá (tốn chi phí nhất) nên cần cân nhắc cẩn thận trước khi sử dụng.

Transaction đơn (single transaction) và transaction phân tán (distributed transaction)

 Một transaction đơn là một transaction làm việc với một nguồn tài nguyên duy nhất (như một database hay một hàng đợi thông điệp), trong khi đó, một transaction phân tán sẽ làm việc với nhiều nguồn tài nguyên. Trong .NET Framework, một transaction đơn thể hiện tất cả các công việc trên một kết nối mở, trong khi đó, một transaction phân tán thể hiện các công việc trên nhiều kết nối. Transaction phân tán dùng giao thức cam kết hai giai đoạn (two-phase commit protocol) và một trình quản lý transaction (transaction manager).

Tạo một transaction

Có hai kiểu transaction là transaction ngầm định (implicit) và transaction tường minh (explicit). Nếu chúng ta không tạo một transaction tường minh thì một transaction ngầm định sẽ tự động được tạo ra.

Tạo một transaction dùng T-SQL

Chúng ta có thể tạo một transaction  tường minh bằng đoạn mã T-SQL sau:


SET XACT_ABORT ON

BEGIN TRY

BEGIN TRANSACTION

--mã thực thi

COMMIT TRANSACTION

END TRY

BEGIN CATCH

ROLLBACK TRANSACTION

--lệnh huỷ

END CATCH

Khối lệnh TRY/CATCH dùng để bắt các lỗi xuất hiện và thực hiện rolled back transaction. Thiết lập XACT_ABORT đến ON để đảm bảo các lỗi dưới mức 21 (severity level 21) được xử lý khi transaction bỏ qua các lỗi. Các lỗi từ 21 trở lên là các lỗi nguy hiểm có thể dừng mã thực thi và rolled back transaction. Phạm vi các transaction được giới hạn trong khối lệnh TRY.

Tạo một transaction dùng đối tượng ADO.NET DbTransaction

Có thể tạo một transaction (tường minh) bằng cách dùng phương thức BeginTransaction của đối tượng DbConnection để tạo một đối tượng DbTransaction. Đoạn mã sau minh hoạ cách tạo một transaction:

Mã VB


Dim cnSetting As ConnectionStringSettings = _

ConfigurationManager.ConnectionStrings("nw")

Using cn As New SqlConnection()

cn.ConnectionString = cnSetting.ConnectionString

cn.Open()

Using tran As SqlTransaction = cn.BeginTransaction()

Try

'mã thực thi

Using cmd As SqlCommand = cn.CreateCommand()

cmd.Transaction = tran

cmd.CommandText = "SELECT count(*) FROM employees"

Dim count As Integer = CInt(cmd.ExecuteScalar())

MessageBox.Show(count.ToString())

End Using

'thực hiện cam kết cho transaction

tran.Commit()

Catch xcp As Exception

tran.Rollback()

'lệnh huỷ

MessageBox.Show(xcp.Message)

End Try

End Using

End Using

Mã C#


ConnectionStringSettings cnSetting =

ConfigurationManager.ConnectionStrings["nw"];

using (SqlConnection cn = new SqlConnection())

{

cn.ConnectionString = cnSetting.ConnectionString;

cn.Open();

using (SqlTransaction tran = cn.BeginTransaction())

{

try

{

//mã thực thi

using (SqlCommand cmd = cn.CreateCommand())

{

cmd.Transaction = tran;

cmd.CommandText = "SELECT count(*) FROM employees";

int count = (int)cmd.ExecuteScalar();

MessageBox.Show(count.ToString());

}

//thực hiện cam kết cho transaction

tran.Commit();

}

catch (Exception xcp)

{

tran.Rollback();

//lệnh huỷ

MessageBox.Show(xcp.Message);

}

}

}

Thiết lập mức độc lập transaction

Thiết lập mức độc lập transaction có một vài cách sau:

  • Thêm câu lệnh SQL đến thủ tục lưu trú (stored procedure)
  • Thêm một gợi ý truy vấn đến lệnh SQL
  • Dùng đối tượng DbTransaction (SqlTransaction cho SQL Server)

Tạo một transaction dùng lớp TransactionScope

Có thể tạo transaction (tường minh) bằng các lớp trong namespace System.Transaction, và lớp phổ biến nhất là lớp TransactionScope. Đoạn mã sau minh hoạ cách tạo một transaction bằng lớp TransactionScope:

Mã VB


Dim cnSetting As ConnectionStringSettings = _

ConfigurationManager.ConnectionStrings("nw")

Using ts As TransactionScope = New TransactionScope()

Using cn As New SqlConnection()

cn.ConnectionString = cnSetting.ConnectionString

cn.Open()

'Mã thực thi

Using cmd As SqlCommand = cn.CreateCommand()

cmd.CommandText = "SELECT count(*) FROM employees"

Dim count As Integer = CInt(cmd.ExecuteScalar())

MessageBox.Show(count.ToString())

End Using

'commit cho transaction

ts.Complete()

End Using

End Using

 Mã C#

ConnectionStringSettings cnSetting =

ConfigurationManager.ConnectionStrings["nw"];

using (TransactionScope ts = new TransactionScope())

{

using (SqlConnection cn = new SqlConnection())

{

cn.ConnectionString = cnSetting.ConnectionString;

cn.Open();

//Mã thực thi

using (SqlCommand cmd = cn.CreateCommand())

{

cmd.CommandText = "SELECT count(*) FROM employees";

int count = (int)cmd.ExecuteScalar();

MessageBox.Show(count.ToString());

}

//commit cho transaction

ts.Complete();

}

}

Tạo một transaction phân tán

Có thể dùng lớp TransactionScope để tạo transaction phân tán. Đoạn mã sau minh hoạ các tạo một transaction dùng hai kết nối:

Mã VB


Dim nwSetting As ConnectionStringSettings = _

ConfigurationManager.ConnectionStrings("nw")

Dim bulkSetting As ConnectionStringSettings = _

ConfigurationManager.ConnectionStrings("BulkCopy")

Using ts As TransactionScope = New TransactionScope()

Using cn As New SqlConnection()

cn.ConnectionString = nwSetting.ConnectionString

cn.Open()

'mã thực thi

Using cmd As SqlCommand = cn.CreateCommand()

cmd.CommandText = _

"Update Products SET UnitsInStock = UnitsInStock -1 " _

& " Where ProductID=1"

cmd.ExecuteNonQuery()

End Using

End Using

Using cn As New SqlConnection()

cn.ConnectionString = bulkSetting.ConnectionString

cn.Open()

'Mã thực thi

Using cmd As SqlCommand = cn.CreateCommand()

cmd.CommandText = _

"Update Products SET UnitsInStock = UnitsInStock +1 "& "_

Where ProductID=2"

cmd.ExecuteNonQuery()

End Using

End Using

' commit cho transaction

ts.Complete()

End Using

Dim dt As New DataTable()

Using cn As New SqlConnection()

cn.ConnectionString = nwSetting.ConnectionString

cn.Open()

'mã thực thi

Using cmd As SqlCommand = cn.CreateCommand()

cmd.CommandText = _

"SELECT ProductID, UnitsInStock FROM Products " _

& " WHERE ProductID = 1"

dt.Load(cmd.ExecuteReader())

End Using

End Using

Using cn As New SqlConnection()

cn.ConnectionString = bulkSetting.ConnectionString

cn.Open()

'Mã thực thi

Using cmd As SqlCommand = cn.CreateCommand()

cmd.CommandText = _

"SELECT ProductID, UnitsInStock FROM Products " _

& " WHERE ProductID = 2"

dt.Load(cmd.ExecuteReader())

End Using

End Using

DataGridView2.DataSource = dt

Mã C#


var nwSetting = ConfigurationManager.ConnectionStrings["nw"];

var bulkSetting = ConfigurationManager.ConnectionStrings["BulkCopy"];

using (var ts = new TransactionScope())

{

using (var cn = new SqlConnection())

{

cn.ConnectionString = nwSetting.ConnectionString;

cn.Open();

//mã thực thi

using (var cmd = cn.CreateCommand())

{

cmd.CommandText =

"Update Products SET UnitsInStock = UnitsInStock -1 "

+ " Where ProductID=1";

cmd.ExecuteNonQuery();

}

}

using (var cn = new SqlConnection())

{

cn.ConnectionString = bulkSetting.ConnectionString;

cn.Open();

//Mã thực thi

using (var cmd = cn.CreateCommand())

{

cmd.CommandText =

"Update Products SET UnitsInStock = UnitsInStock +1 "

+ " Where ProductID=2";

cmd.ExecuteNonQuery();

}

}

// commit cho transactio

ts.Complete();

}

var dt = new DataTable();

using (var cn = new SqlConnection())

{

cn.ConnectionString = nwSetting.ConnectionString;

cn.Open();

//mã thực thi

using (var cmd = cn.CreateCommand())

{

cmd.CommandText =

"SELECT ProductID, UnitsInStock FROM Products "

+ " WHERE ProductID = 1";

dt.Load(cmd.ExecuteReader());

}

}

using (var cn = new SqlConnection())

{

cn.ConnectionString = bulkSetting.ConnectionString;

cn.Open();

//Mã thực thi

using (var cmd = cn.CreateCommand())

{

cmd.CommandText =

"SELECT ProductID, UnitsInStock FROM Products "

+ " WHERE ProductID = 2";

dt.Load(cmd.ExecuteReader());

}

}

dataGridView2.DataSource = dt;

Xem transaction phân tán

Các transaction phân tán được quản lý bởi Bộ định vị giao dịch phân tán (Distributed Transaction Coordinator – DCT) và chúng ta có thể xem trong hệ điều hành Windows bằng cách vào Start > Control Panel > Administrative Tools > Component Services > Computers > My Computer > Distributed Tansaction Coordinator > LocalDTC > Transaction Statistic. Trong hệ điều hành Windows 7: