Trong khoa học máy tính có các khái niệm ACID [Nguyên tử, Tính nhất quán, Cách ly, Độ bền], nguyên tử và tính toàn vẹn tham chiếu. Thuật ngữ nào bạn biết, phần lớn phụ thuộc vào độ tuổi của bạn. Tôi biết người cuối cùng, điều này cho bạn manh mối để hiểu tôi già
Về cơ bản tất cả các thuật ngữ này có nghĩa giống nhau. điều đó có nghĩa là máy tính sẽ thực hiện bất kỳ giao dịch nào dẫn đến một tập hợp các giao dịch không nhất quán về mặt logic
[Bài viết này là một phần của Hướng dẫn MongoDB của chúng tôi. Sử dụng menu bên phải để điều hướng. ]
Tại sao nó lại quan trọng?
Hãy tưởng tượng nếu bạn có một đơn đặt hàng và hệ thống kiểm soát hàng tồn kho. Khi bạn bán hàng, nó sẽ làm giảm hàng tồn kho trong tay. Nhưng điều gì xảy ra nếu giao dịch bán hàng hoạt động sau đó cập nhật hàng tồn kho không thành công? . hàng tồn kho sẽ không khớp với doanh số bán hàng
Cách để tránh điều đó là nhóm hai giao dịch thành một giao dịch lớn hơn. Các lập trình viên cơ sở dữ liệu Oracle biết đây là câu lệnh bắt đầu và cam kết. MongoDB hỗ trợ cùng một khái niệm mà chúng tôi minh họa bên dưới
Lần truy cập hiệu suất
Mặc dù MongoDB hỗ trợ các giao dịch, nhưng nó nhấn mạnh rằng do các vấn đề về hiệu suất, tốt hơn hết là nhúng tài liệu dưới dạng mảng JSON. Nói cách khác, đặt tất cả dữ liệu cho một mục và dữ liệu liên quan của nó vào một tài liệu
Các lập trình viên của Oracle sẽ nói rằng kết quả là cơ sở dữ liệu không được chuẩn hóa. Nhưng điều đó không thành vấn đề, vì cơ sở dữ liệu dữ liệu lớn đã loại bỏ khái niệm đó, nói rằng có thể lưu trữ cùng một dữ liệu trong hai bản ghi, vì trọng tâm của chúng là tốc độ và việc sử dụng thêm dung lượng đĩa để lưu trữ thứ gì đó hai lần không thành vấn đề, vì
Nói cách khác, bạn có thể có mã zip 29607 và từ Greenville, SC trong hai bản ghi. Bạn không cần giữ một bảng mã zip như một thực thể riêng biệt, như cách bạn làm với phương pháp rdbms
Minh họa giao dịch
Bạn cần cài đặt MongoDB theo cụm để thực hiện việc này, nhưng bạn có thể giả vờ rằng bạn có một cụm chỉ có máy chủ trên máy tính xách tay của mình. Chỉ cần giả vờ rằng các bản sao đang ngoại tuyến, bằng cách không cài đặt mà chỉ cài đặt một nút. Nói cách khác, hãy làm theo các hướng dẫn mà chúng tôi đã viết, nhưng xóa phần trong /etc/mongodConfig. conf như hình bên dưới và chỉ thiết lập một máy chủ
sharding: clusterRole: configsvr
Để sử dụng các giao dịch, bạn phải bật sao chép và tắt phân đoạn. Hỗ trợ cho cơ sở dữ liệu được phân đoạn đi kèm với MongoDB 4. 2. Dưới đây chúng tôi sử dụng 4. 0. 5
Tạo một số dữ liệu
Mở vỏ mongo và tạo các sản phẩm và bộ sưu tập bán hàng, sau đó thêm một sản phẩm vào kho. Chúng tôi thêm 100 mặt hàng của sản phẩm 123
use products db.createCollection["products"] db.createCollection["sales"] db.products.insert[{product: 123, count: 100}]
Bây giờ, bán 4 mặt hàng của sản phẩm 123. Kết quả sẽ là 96 mặt hàng còn lại trong kho. Vì vậy, hãy thêm một giao dịch bán hàng và cập nhật số lượng hàng tồn kho thành 96 được hiển thị bên dưới
Either paste that code into the MongoDB shell or save it in a JavaScript text file and then run it with load[‘’].
session = db.getMongo[].startSession[ { readPreference: { mode: "primary" } } ]; productsCollection = session.getDatabase["products"].products; salesCollection = session.getDatabase["products"].sales; session.startTransaction[ { readConcern: { level: "snapshot" }, writeConcern: { w: "majority" } } ]; try { salesCollection.insertOne[ { product: 123, count: 4 } ]; productsCollection.update[ { product: 123 }, { $set: { count: 96 } } ]; } catch [error] { session.abortTransaction[]; throw error; } session.commitTransaction[]; session.endSession[];
Giải thích mã
Đầu tiên, kiểm tra đã hoạt động
db.products.findOne[]; { "_id" : ObjectId["5cb8ccb664ae78fe855e9431"], "product" : 123, "count" : 96 }
Các phần chính của mã là
phiên họp. startTransaction[ { readConcern. { cấp độ. “ảnh chụp nhanh” }, writeConcern. {w. “đa số” } } ];Bắt đầu giao dịch. phiên họp. cam kếtTransaction[];phiên họp. Phiên kết thúc[];
{ sản phẩm. 123 },
{ $set
{
đếm. 96
}
}
] Định dạng của cái này là db. thu thập. cập nhật [truy vấn tìm bản ghi, cập nhật trường nào]
Chúng tôi cập nhật sản phẩm 123 và đặt số đếm thành 96
Xử lý sự cố
Nếu bạn đang cố thực hiện điều này với cài đặt hiện tại của mình và không bật sao chép hoặc tắt phân đoạn hoặc thực hiện một số câu lệnh phi logic trong mã của mình, thì bạn sẽ gặp phải bất kỳ lỗi nào trong số này
Phiên bản MongoDB mới nhất 4. 2 giao dịch đa tài liệu được giới thiệu. Đây là một tính năng quan trọng bị thiếu trong hầu hết các cơ sở dữ liệu NoSQL [và các cơ sở dữ liệu SQL đã khoe khoang về điều đó]
Một giao dịch, có thể bao gồm một hoặc nhiều hoạt động, hoạt động như một hoạt động nguyên tử. Nếu tất cả các thao tác con thành công, giao dịch đó được coi là hoàn thành. Nếu không nó không thành công
Đây được gọi là tính nguyên tử. Đây là một khái niệm quan trọng cần hiểu để giữ cho dữ liệu của bạn nhất quán khi đọc/ghi dữ liệu đồng thời
Phạm Vi Bài Viết Và Mục Tiêu
Mục tiêu của bài viết này là cung cấp cho bạn một ví dụ thực tế trong đó dữ liệu không nhất quán xảy ra mà không có giao dịch. Sau đó, chúng tôi sẽ xây dựng một giải pháp trong Java bằng cách sử dụng Giao dịch MongoDB để ngăn chặn chúng
Làm như vậy, bạn sẽ học cách
- Tránh các Điều kiện cuộc đua có thể dẫn đến sự không thống nhất về dữ liệu
- Xây dựng các ứng dụng linh hoạt hơn bằng cách sử dụng tính năng Ghi có thể thử lại tích hợp sẵn của Mongo
Ngoài ra, tôi đã thêm một hàm bao bọc, static R withTransaction[final Function executeFn];
, mà bạn có thể sử dụng để cải thiện khả năng đọc mã
Thí dụ. Cách xử lý các giao dịch đồng thời trên cùng một tài khoản ngân hàng
Giả sử bạn và người phối ngẫu của bạn chia sẻ một tài khoản ngân hàng chung. Mỗi bạn đến cây ATM cùng một lúc và bắt đầu rút tiền
t1 -> You: Press check balance. ATM shows 100 dollars
t2 -> Spouse: Press check balance. ATM shows 100 dollars
t3 -> You & Spouse: withdraw 10 dollars
t4 -> Bank: initializes P1 and P2 to handle your and your spouse's requests.
t5 -> P1 and P2 checked the balance and saw 100 dollars
t6 -> P1 and P2 subtracted 10 dollars from the balance
t7 -> P1 updated the DB with the new balance of 90
t8 -> P2 updated the DB with the new balance of 90
t1 - t8 là dòng thời gian của các sự kiện. P1 và P2 là một quy trình xử lý các yêu cầu từ các máy ATM của Ngân hàngTrong ví dụ trên, các hoạt động không diễn ra tuần tự. Quá trình của ngân hàng P2 không đợi P1 hoàn thành nhiệm vụ của mình. Nếu ngân hàng đã đợi P1 đọc xong số dư, tính toán số dư mới và ghi số dư cập nhật trở lại DB trước khi đọc số dư mới nhất, thì ngân hàng sẽ không bị mất 10 đô la
Giải pháp cho vấn đề này là các giao dịch. Bạn có thể coi chúng tương tự như các khối Khóa, Semaphores và Đồng bộ hóa trong Java. Trong Java, nó đảm bảo rằng chỉ người giữ Khóa mới thực thi mã được bảo vệ bởi khóa
Cách thiết lập chức năng trợ giúp
Bây giờ chúng ta hãy đến phần mã hóa. Tôi sẽ cho rằng bạn đã có thiết lập MongoClient. Bạn sẽ cần
final static MongoClient client; // assumed you initialized this somewhere
public static ClientSession getNewClientSession[] {
return client.startSession[];
}
public static TransactionOptions getTransactionOptions[] {
return TransactionOptions.builder[]
.readPreference[ReadPreference.primary[]]
.readConcern[ReadConcern.LOCAL]
.writeConcern[WriteConcern.MAJORITY]
.build[];
}
Một số chức năng chung cần thiết ví dụ dưới đâygetNewClientSession
chỉ cần trả về một phiên cho một giao dịch. ClientSession
là mã định danh cho một giao dịch cụ thể. Đây là một phần dữ liệu quan trọng mà bạn chuyển vào tất cả các hoạt động Mongo sau để nó có thể tách biệt các hoạt động
getTransactionOptions
cung cấp các tùy chọn cho Giao dịch. ReadPreference.primary[]
cung cấp cho chúng tôi thông tin cập nhật nhất về một cụm khi chúng tôi đang đọc dữ liệu.
final static MongoClient client; // assumed you initialized this somewhere
public static ClientSession getNewClientSession[] {
return client.startSession[];
}
public static TransactionOptions getTransactionOptions[] {
return TransactionOptions.builder[]
.readPreference[ReadPreference.primary[]]
.readConcern[ReadConcern.LOCAL]
.writeConcern[WriteConcern.MAJORITY]
.build[];
}
0 dẫn đến việc DB thừa nhận một cam kết sau khi nó ghi thành công vào phần lớn các máy chủThay vì tạo các phiên khách hàng và các tùy chọn giao dịch ở khắp mọi nơi, thay vào đó chúng ta nên thực hiện nó trên một phương thức duy nhất và chỉ chuyển các hàm cần tính nguyên tử cho nó
static R withTransaction[final Function executeFn] {
final ClientSession clientSession = getNewClientSession[];
TransactionOptions txnOptions = this.getTransactionOptions[];
TransactionBody txnBody = new TransactionBody[] {
public R execute[] {
return executeFn.apply[clientSession];
}
};
try {
return clientSession.withTransaction[txnBody, txnOptions];
} catch [RuntimeException e] {
e.printStackTrace[];
} finally {
clientSession.close[];
}
return null;
}
Hàm chung để thực thi Hàm trong một giao dịchHàm trên chạy các hoạt động bên trong hàm được truyền vào, đối số
final static MongoClient client; // assumed you initialized this somewhere
public static ClientSession getNewClientSession[] {
return client.startSession[];
}
public static TransactionOptions getTransactionOptions[] {
return TransactionOptions.builder[]
.readPreference[ReadPreference.primary[]]
.readConcern[ReadConcern.LOCAL]
.writeConcern[WriteConcern.MAJORITY]
.build[];
}
1, dưới dạng hoạt động nguyên tử hoặc giao dịch. Hãy triển khai chức năng rút tiền của chúng ta bằng các giao dịchLưu ý rằng tôi đang trở lại
final static MongoClient client; // assumed you initialized this somewhere
public static ClientSession getNewClientSession[] {
return client.startSession[];
}
public static TransactionOptions getTransactionOptions[] {
return TransactionOptions.builder[]
.readPreference[ReadPreference.primary[]]
.readConcern[ReadConcern.LOCAL]
.writeConcern[WriteConcern.MAJORITY]
.build[];
}
2. Bạn chỉ có thể đưa ra một ngoại lệ mới để cho người gọi biết rằng giao dịch đã thất bại. Vì lợi ích của ví dụ này, việc trả về null có nghĩa là giao dịch thất bạiVí dụ về tài khoản ngân hàng trong Java
public class Account {
@BsonId
ObjectId _id;
int balance;
.. getters and setters
}
public class AccountService {
public Collection getAccounts[] {
return dbClient.getCollection['account', Account.class];
}
private Account currentBalance[ClientSession session, Bson accountId] {
return getAccounts[].findOne[session, Filters.eq['_id', accountId]].first[];
}
private int currentBalance[ClientSession session, Bson accountId] {
Account account = getAccounts[].findOne[session, Filters.eq['_id', accountId]].first[];
return account.balance;
}
private int updateBalance[ClientSession session, Bson accountId, int newBalance] {
Account account = getAccounts[].updateOne[session, Filters.eq['_id', accountId], Updates.set['balance', newBalance]].first[];
return account.balance;
}
public Account drawCash[ClientSession session, Bson accountId, int amount]{
int currentBalance = this.currentBalance[accountId];
int newBalance = currentBalance - amount;
return updateBalance[session, accountId, amount];
}
}
Ghi chú. các trường hợp cạnh chẳng hạn như kiểm tra xem số dư có lớn hơn số tiền rút không được kiểm tra để đơn giảnTrong đoạn mã trên, lớp
final static MongoClient client; // assumed you initialized this somewhere
public static ClientSession getNewClientSession[] {
return client.startSession[];
}
public static TransactionOptions getTransactionOptions[] {
return TransactionOptions.builder[]
.readPreference[ReadPreference.primary[]]
.readConcern[ReadConcern.LOCAL]
.writeConcern[WriteConcern.MAJORITY]
.build[];
}
3 là một mô hình lớp Java đơn giản cho tài khoản của người dùng. final static MongoClient client; // assumed you initialized this somewhere
public static ClientSession getNewClientSession[] {
return client.startSession[];
}
public static TransactionOptions getTransactionOptions[] {
return TransactionOptions.builder[]
.readPreference[ReadPreference.primary[]]
.readConcern[ReadConcern.LOCAL]
.writeConcern[WriteConcern.MAJORITY]
.build[];
}
4 là trình truy cập cơ sở dữ liệu cho bộ sưu tập tài khoản. Phương pháp final static MongoClient client; // assumed you initialized this somewhere
public static ClientSession getNewClientSession[] {
return client.startSession[];
}
public static TransactionOptions getTransactionOptions[] {
return TransactionOptions.builder[]
.readPreference[ReadPreference.primary[]]
.readConcern[ReadConcern.LOCAL]
.writeConcern[WriteConcern.MAJORITY]
.build[];
}
5 hoàn thành tập hợp các hoạt động được thực hiện bởi một quy trình duy nhất [P1 hoặc P2] được mô tả trong ví dụ đầu tiên để phân phối tiền cho bạn hoặc vợ/chồng của bạnBây giờ chúng ta sử dụng hàm
final static MongoClient client; // assumed you initialized this somewhere
public static ClientSession getNewClientSession[] {
return client.startSession[];
}
public static TransactionOptions getTransactionOptions[] {
return TransactionOptions.builder[]
.readPreference[ReadPreference.primary[]]
.readConcern[ReadConcern.LOCAL]
.writeConcern[WriteConcern.MAJORITY]
.build[];
}
6 này để gọi final static MongoClient client; // assumed you initialized this somewhere
public static ClientSession getNewClientSession[] {
return client.startSession[];
}
public static TransactionOptions getTransactionOptions[] {
return TransactionOptions.builder[]
.readPreference[ReadPreference.primary[]]
.readConcern[ReadConcern.LOCAL]
.writeConcern[WriteConcern.MAJORITY]
.build[];
}
7.. Some REST API
AccountService accountService = ...; // Dependency injected
@Path['/account/withdraw'] // Endpoint to withdraw money
withdrawMoney[] {
ObjectId accountId = ...// some method to get current users account ID
Account account = withTransaction[new Function[] {
@Override
public Workflow apply[ClientSession clientSession] {
// Everything inside this block run with in the same transaction as long as you pass the argument clientSession to mongo
accountService.drawCash[clientSession, accountId, 10];
}
}];
if[Objects.isNull[account]]{
return "Failed to withdraw money";
}
return "New account balance is " + account.balance;
}
Bây giờ, nếu bạn gọi điểm cuối này hai lần, đồng thời, một người dùng sẽ thấy số dư cuối cùng là 90 và người dùng thứ hai sẽ thấy 80
Bạn có thể đoán rằng giao dịch của người dùng thứ hai đã thất bại. Vâng, nó đã làm. Nhưng MongoDB có cơ chế thử lại tích hợp và nó đã tự động thử lại thao tác thứ hai của chúng tôi và thành công
Trường hợp sử dụng ví dụ trong thế giới thực
Chúng tôi sử dụng các giao dịch trên PS2PDF của mình. com chuyển đổi video trực tuyến để ngăn một luồng ghi đè trạng thái quy trình được cập nhật bởi luồng khác
Ví dụ: đối với mỗi quy trình chuyển đổi video, chúng tôi tạo một tài liệu có tên là Công việc trên DB. Nó có một trường trạng thái có thể nhận các giá trị như
final static MongoClient client; // assumed you initialized this somewhere
public static ClientSession getNewClientSession[] {
return client.startSession[];
}
public static TransactionOptions getTransactionOptions[] {
return TransactionOptions.builder[]
.readPreference[ReadPreference.primary[]]
.readConcern[ReadConcern.LOCAL]
.writeConcern[WriteConcern.MAJORITY]
.build[];
}
8, final static MongoClient client; // assumed you initialized this somewhere
public static ClientSession getNewClientSession[] {
return client.startSession[];
}
public static TransactionOptions getTransactionOptions[] {
return TransactionOptions.builder[]
.readPreference[ReadPreference.primary[]]
.readConcern[ReadConcern.LOCAL]
.writeConcern[WriteConcern.MAJORITY]
.build[];
}
9 và static R withTransaction[final Function executeFn] {
final ClientSession clientSession = getNewClientSession[];
TransactionOptions txnOptions = this.getTransactionOptions[];
TransactionBody txnBody = new TransactionBody[] {
public R execute[] {
return executeFn.apply[clientSession];
}
};
try {
return clientSession.withTransaction[txnBody, txnOptions];
} catch [RuntimeException e] {
e.printStackTrace[];
} finally {
clientSession.close[];
}
return null;
}
0Khi chủ đề đã cập nhật Công việc. trạng thái trên DB thành
static R withTransaction[final Function executeFn] {
final ClientSession clientSession = getNewClientSession[];
TransactionOptions txnOptions = this.getTransactionOptions[];
TransactionBody txnBody = new TransactionBody[] {
public R execute[] {
return executeFn.apply[clientSession];
}
};
try {
return clientSession.withTransaction[txnBody, txnOptions];
} catch [RuntimeException e] {
e.printStackTrace[];
} finally {
clientSession.close[];
}
return null;
}
0, chúng tôi không muốn bất kỳ chuỗi chậm nào hoàn nguyên thông báo đó thành final static MongoClient client; // assumed you initialized this somewhere
public static ClientSession getNewClientSession[] {
return client.startSession[];
}
public static TransactionOptions getTransactionOptions[] {
return TransactionOptions.builder[]
.readPreference[ReadPreference.primary[]]
.readConcern[ReadConcern.LOCAL]
.writeConcern[WriteConcern.MAJORITY]
.build[];
}
9. Một khi công việc đã hoàn thành thì không thể thay đổiChúng tôi sử dụng phương pháp
final static MongoClient client; // assumed you initialized this somewhere
public static ClientSession getNewClientSession[] {
return client.startSession[];
}
public static TransactionOptions getTransactionOptions[] {
return TransactionOptions.builder[]
.readPreference[ReadPreference.primary[]]
.readConcern[ReadConcern.LOCAL]
.writeConcern[WriteConcern.MAJORITY]
.build[];
}
6 đã đề cập ở trên để đảm bảo rằng không có hoạt động nào ghi đè trạng thái static R withTransaction[final Function executeFn] {
final ClientSession clientSession = getNewClientSession[];
TransactionOptions txnOptions = this.getTransactionOptions[];
TransactionBody txnBody = new TransactionBody[] {
public R execute[] {
return executeFn.apply[clientSession];
}
};
try {
return clientSession.withTransaction[txnBody, txnOptions];
} catch [RuntimeException e] {
e.printStackTrace[];
} finally {
clientSession.close[];
}
return null;
}
4Phần kết luận
Tôi hy vọng bây giờ bạn có thể sử dụng các giao dịch để tránh các điều kiện cạnh tranh trên các ứng dụng của mình. Ngoài ra, hãy sử dụng
static R withTransaction[final Function executeFn] {
final ClientSession clientSession = getNewClientSession[];
TransactionOptions txnOptions = this.getTransactionOptions[];
TransactionBody txnBody = new TransactionBody[] {
public R execute[] {
return executeFn.apply[clientSession];
}
};
try {
return clientSession.withTransaction[txnBody, txnOptions];
} catch [RuntimeException e] {
e.printStackTrace[];
} finally {
clientSession.close[];
}
return null;
}
5 và static R withTransaction[final Function executeFn] {
final ClientSession clientSession = getNewClientSession[];
TransactionOptions txnOptions = this.getTransactionOptions[];
TransactionBody txnBody = new TransactionBody[] {
public R execute[] {
return executeFn.apply[clientSession];
}
};
try {
return clientSession.withTransaction[txnBody, txnOptions];
} catch [RuntimeException e] {
e.printStackTrace[];
} finally {
clientSession.close[];
}
return null;
}
6 tích hợp sẵn để cải thiện khả năng chịu lỗiTôi nên chỉ ra rằng, Giao dịch MongoDB còn khá mới và có những bài viết xác định một số điểm không nhất quán xảy ra trong các trường hợp đặc biệt. Nhưng rất có thể bạn sẽ gặp phải những vấn đề này
QUẢNG CÁO
QUẢNG CÁO
QUẢNG CÁO
QUẢNG CÁO
QUẢNG CÁO
QUẢNG CÁO
QUẢNG CÁO
Đọc thêm bài viết
Nếu bài viết này hữu ích, hãy tweet nó
Học cách viết mã miễn phí. Chương trình giảng dạy mã nguồn mở của freeCodeCamp đã giúp hơn 40.000 người có được việc làm với tư cách là nhà phát triển. Bắt đầu