Giao dịch
SQLite hỗ trợ transactions
— các tập hợp hành động liên quan logic với nhau, có thể được thực hiện hoàn toàn hoặc không thực hiện chút nào, điều này đảm bảo tính nhất quán của dữ liệu trong cơ sở dữ liệu.
Khái niệm
transaction
mang một ý nghĩa mới trong bối cảnh cơ sở dữ liệu, khác với những gì chúng ta đã mô tả trong giao dịch thương mại. Một giao dịch thương mại nghĩa là một hoạt động riêng lẻ trên các thực thể của tài khoản giao dịch, bao gồm lệnh, giao dịch và vị thế.
Giao dịch cung cấp 4 đặc điểm chính của sự thay đổi cơ sở dữ liệu:
- Nguyên tử (không thể chia cắt) — khi giao dịch hoàn tất thành công, tất cả các thay đổi trong đó sẽ được ghi vào cơ sở dữ liệu, và nếu có lỗi xảy ra, không có gì được ghi vào.
- Nhất quán — trạng thái đúng hiện tại của cơ sở chỉ có thể chuyển sang một trạng thái đúng khác (các trạng thái trung gian, theo logic ứng dụng, bị loại trừ).
- Cách ly — các thay đổi trong giao dịch của kết nối hiện tại không hiển thị cho đến khi giao dịch này kết thúc trong các kết nối khác đến cùng cơ sở dữ liệu và ngược lại, các thay đổi từ các kết nối khác không hiển thị trong kết nối hiện tại khi còn một giao dịch chưa hoàn tất.
- Bền vững — các thay đổi từ một giao dịch thành công được đảm bảo lưu trữ trong cơ sở dữ liệu.
Các thuật ngữ cho những đặc điểm này — Nguyên tử, Nhất quán, Cách ly và Bền vững — tạo thành từ viết tắt ACID, nổi tiếng trong lý thuyết cơ sở dữ liệu.
Ngay cả khi quá trình bình thường của chương trình bị gián đoạn do lỗi hệ thống, cơ sở dữ liệu vẫn giữ được trạng thái hoạt động.
Thông thường, việc sử dụng giao dịch được minh họa bằng ví dụ về hệ thống ngân hàng, trong đó tiền được chuyển từ tài khoản của một khách hàng sang tài khoản của khách hàng khác. Việc này ảnh hưởng đến hai bản ghi số dư khách hàng: một bản ghi giảm số dư theo số tiền chuyển, và bản ghi kia tăng lên. Một tình huống mà chỉ một trong các thay đổi này được áp dụng sẽ làm mất cân bằng tài khoản ngân hàng: tùy thuộc vào thao tác nào thất bại, số tiền chuyển có thể biến mất hoặc ngược lại, xuất hiện từ hư không.
Cũng có thể đưa ra một ví dụ gần gũi hơn với thực tiễn giao dịch nhưng dựa trên nguyên tắc "ngược lại". Thực tế là hệ thống kế toán cho lệnh, giao dịch và vị thế trong MetaTrader 5 không mang tính giao dịch.
Cụ thể, như chúng ta biết từ chương về Tạo Expert Advisors, một lệnh được kích hoạt (thị trường hoặc chờ xử lý), không còn trong danh sách hoạt động, có thể không xuất hiện ngay lập tức trong danh sách vị thế. Do đó, để phân tích kết quả thực tế, cần triển khai trong chương trình MQL sự mong đợi cập nhật (thực tế hóa) môi trường giao dịch. Nếu hệ thống kế toán dựa trên giao dịch, thì việc thực hiện lệnh, đăng ký giao dịch trong lịch sử và sự xuất hiện của vị thế sẽ được bao gồm trong một giao dịch và phối hợp với nhau. Các nhà phát triển terminal đã chọn cách tiếp cận khác: trả lại bất kỳ sửa đổi nào của môi trường giao dịch nhanh nhất và không đồng bộ, và tính toàn vẹn của chúng phải được chương trình MQL giám sát.
Bất kỳ lệnh SQL nào thay đổi cơ sở (tức là, thực tế là mọi thứ trừ SELECT
) sẽ tự động được bao bọc trong một giao dịch nếu điều này chưa được thực hiện rõ ràng trước đó.
API MQL5 cung cấp 3 hàm để quản lý giao dịch: DatabaseTransactionBegin
, DatabaseTransactionCommit
, và DatabaseTransactionRollback
. Tất cả các hàm trả về true
nếu thành công hoặc false
nếu có lỗi.
bool DatabaseTransactionBegin(int database)
Hàm DatabaseTransactionBegin
bắt đầu thực hiện một giao dịch trong cơ sở dữ liệu với mô tả được chỉ định, thu được từ DatabaseOpen
(liên kết).
Tất cả các thay đổi tiếp theo được thực hiện trên cơ sở dữ liệu được tích lũy trong bộ nhớ đệm giao dịch nội bộ và không được ghi vào cơ sở dữ liệu cho đến khi hàm DatabaseTransactionCommit
được gọi.
Giao dịch trong MQL5 không thể lồng nhau: nếu một giao dịch đã được bắt đầu, thì việc gọi lại DatabaseTransactionBegin
sẽ trả về cờ lỗi và xuất thông báo vào nhật ký.
database error, cannot start a transaction within a transaction
DatabaseTransactionBegin(db)=false / DATABASE_ERROR(5601)
2
Tương ứng, bạn không thể cố gắng hoàn tất giao dịch nhiều lần.
bool DatabaseTransactionCommit(int database)
Hàm DatabaseTransactionCommit
kết thúc một giao dịch đã bắt đầu trước đó trong cơ sở dữ liệu với handle được chỉ định và áp dụng tất cả các thay đổi tích lũy (lưu chúng). Nếu một chương trình MQL bắt đầu một giao dịch nhưng không áp dụng nó trước khi đóng cơ sở dữ liệu, tất cả các thay đổi sẽ bị mất.
Nếu cần, chương trình có thể hủy giao dịch, và do đó tất cả các thay đổi kể từ khi bắt đầu giao dịch.
bool DatabaseTransactionRollback(int database)
Hàm DatabaseTransactionRollback
thực hiện "quay lại" tất cả các hành động bao gồm trong giao dịch đã bắt đầu trước đó cho cơ sở dữ liệu với handle database
.
Hãy hoàn thiện các phương thức của lớp DBSQLite
để làm việc với giao dịch, tính đến hạn chế về việc lồng ghép chúng, mà chúng ta sẽ tính toán trong biến transaction
. Nếu nó là 0, phương thức begin
bắt đầu một giao dịch bằng cách gọi DatabaseTransactionBegin
. Tất cả các lần thử bắt đầu giao dịch sau đó chỉ đơn giản là tăng bộ đếm. Trong phương thức commit
, chúng ta giảm bộ đếm, và khi nó đạt 0, chúng ta gọi DatabaseTransactionCommit
.
class DBSQLite
{
protected:
int transaction;
...
public:
bool begin()
{
if(transaction > 0) // đã trong giao dịch
{
transaction++; // theo dõi mức độ lồng ghép
return true;
}
return (bool)(transaction = PRTF(DatabaseTransactionBegin(handle)));
}
bool commit()
{
if(transaction > 0)
{
if(--transaction == 0) // giao dịch ngoài cùng
return PRTF(DatabaseTransactionCommit(handle));
}
return false;
}
bool rollback()
{
if(transaction > 0)
{
if(--transaction == 0)
return PRTF(DatabaseTransactionRollback(handle));
}
return false;
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Hãy tạo lớp DBTransaction
, lớp này sẽ cho phép mô tả các đối tượng bên trong các khối (ví dụ, hàm) đảm bảo tự động bắt đầu một giao dịch với việc áp dụng (hoặc hủy bỏ) tiếp theo khi chương trình thoát khỏi khối.
class DBTransaction
{
DBSQLite *db;
const bool autocommit;
public:
DBTransaction(DBSQLite &owner, const bool c = false): db(&owner), autocommit(c)
{
if(CheckPointer(db) != POINTER_INVALID)
{
db.begin();
}
}
~DBTransaction()
{
if(CheckPointer(db) != POINTER_INVALID)
{
autocommit ? db.commit() : db.rollback();
}
}
bool commit()
{
if(CheckPointer(db) != POINTER_INVALID)
{
const bool done = db.commit();
db = NULL;
return done;
}
return false;
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Chính sách sử dụng các đối tượng như vậy loại bỏ nhu cầu xử lý các tùy chọn khác nhau để thoát khỏi một khối (hàm).
void DataFunction(DBSQLite &db)
{
DBTransaction tr(db);
DBQuery *query = db.prepare("UPDATE..."); // thay đổi hàng loạt
... // sửa đổi cơ sở
if(... /* error1 */) return; // tự động quay lại
... // sửa đổi cơ sở
if(... /* error2 */) return; // tự động quay lại
tr.commit();
}
2
3
4
5
6
7
8
9
10
Để một đối tượng tự động áp dụng các thay đổi ở bất kỳ giai đoạn nào, hãy truyền true
vào tham số thứ hai của hàm tạo của nó.
void DataFunction(DBSQLite &db)
{
DBTransaction tr(db, true);
DBQuery *query = db.prepare("UPDATE..."); // thay đổi hàng loạt
... // sửa đổi cơ sở
if(... /* condition1 */) return; // tự động áp dụng
... // sửa đổi cơ sở
if(... /* condition2 */) return; // tự động áp dụng
...
} // tự động áp dụng
2
3
4
5
6
7
8
9
10
Bạn có thể mô tả đối tượng DBTransaction
bên trong vòng lặp và sau đó, tại mỗi lần lặp, một giao dịch riêng biệt sẽ bắt đầu và đóng lại.
Một minh họa về giao dịch sẽ được trình bày trong phần Ví dụ về tìm kiếm chiến lược giao dịch bằng SQLite.