Tôi có thể thực hiện các giao dịch và khóa trong CouchDB không?


81

Tôi cần thực hiện các giao dịch (bắt đầu, cam kết hoặc khôi phục), khóa (chọn để cập nhật). Làm thế nào tôi có thể làm điều đó trong một mô hình tài liệu db?

Biên tập:

Trường hợp này là:

  • Tôi muốn chạy một trang web đấu giá.
  • Và tôi nghĩ cách mua hàng trực tiếp.
  • Khi mua hàng trực tiếp, tôi phải giảm trường số lượng trong hồ sơ mặt hàng, nhưng chỉ khi số lượng lớn hơn 0. Đó là lý do tại sao tôi cần khóa và giao dịch.
  • Tôi không biết làm thế nào để giải quyết vấn đề đó mà không có khóa và / hoặc giao dịch.

Tôi có thể giải quyết vấn đề này với CouchDB không?

Câu trả lời:


145

CouchDB sử dụng mô hình "đồng thời lạc quan". Nói một cách đơn giản nhất, điều này chỉ có nghĩa là bạn gửi một phiên bản tài liệu cùng với bản cập nhật của mình và CouchDB từ chối thay đổi nếu phiên bản tài liệu hiện tại không khớp với những gì bạn đã gửi.

Nó thực sự đơn giản. Bạn có thể điều chỉnh lại nhiều tình huống dựa trên giao dịch thông thường cho CouchDB. Tuy nhiên, bạn cần phải loại bỏ kiến ​​thức miền RDBMS của mình khi học CouchDB. Sẽ hữu ích khi tiếp cận các vấn đề từ cấp độ cao hơn, thay vì cố gắng đưa Couch vào thế giới dựa trên SQL.

Theo dõi hàng tồn kho

Vấn đề bạn nêu ra chủ yếu là vấn đề hàng tồn kho. Nếu bạn có tài liệu mô tả một mặt hàng và nó bao gồm trường "số lượng có sẵn", bạn có thể xử lý các vấn đề đồng thời như sau:

  1. Lấy tài liệu, ghi lại _revtài sản mà CouchDB gửi cùng
  2. Giảm trường số lượng, nếu nó lớn hơn 0
  3. Gửi lại tài liệu đã cập nhật, sử dụng thuộc _revtính
  4. Nếu _revkết quả khớp với số hiện đang được lưu trữ, hãy hoàn tất!
  5. Nếu có xung đột (khi _revkhông khớp), hãy truy xuất phiên bản tài liệu mới nhất

Trong trường hợp này, có hai trường hợp thất bại có thể xảy ra. Nếu phiên bản tài liệu gần đây nhất có số lượng bằng 0, bạn xử lý nó giống như bạn làm trong RDBMS và cảnh báo cho người dùng rằng họ thực sự không thể mua thứ họ muốn mua. Nếu phiên bản tài liệu gần đây nhất có số lượng lớn hơn 0, bạn chỉ cần lặp lại thao tác với dữ liệu đã cập nhật và bắt đầu lại từ đầu. Điều này buộc bạn phải thực hiện nhiều công việc hơn một chút so với RDBMS và có thể gây khó chịu một chút nếu có các bản cập nhật thường xuyên, xung đột.

Bây giờ, câu trả lời tôi chỉ đưa ra giả định rằng bạn sẽ làm mọi thứ trong CouchDB giống như cách bạn làm trong RDBMS. Tôi có thể tiếp cận vấn đề này hơi khác một chút:

Tôi sẽ bắt đầu với tài liệu "sản phẩm chính" bao gồm tất cả dữ liệu mô tả (tên, hình ảnh, mô tả, giá, v.v.). Sau đó, tôi sẽ thêm tài liệu "phiếu kiểm kê" cho từng trường hợp cụ thể, với các trường cho product_keyclaimed_by. Nếu bạn đang bán một mô hình búa, và có 20 trong số họ để bán, bạn có thể có văn bản với các phím như hammer-1, hammer-2, vv, để đại diện cho mỗi búa sẵn.

Sau đó, tôi sẽ tạo một dạng xem cung cấp cho tôi danh sách các búa có sẵn, với chức năng giảm cho phép tôi xem "tổng số". Những thứ này hoàn toàn không nằm ngoài vòng bít, nhưng sẽ cung cấp cho bạn ý tưởng về chế độ xem làm việc sẽ như thế nào.

Bản đồ

function(doc) 
{ 
    if (doc.type == 'inventory_ticket' && doc.claimed_by == null ) { 
        emit(doc.product_key, { 'inventory_ticket' :doc.id, '_rev' : doc._rev }); 
    } 
}

Điều này cung cấp cho tôi danh sách các "vé" có sẵn, theo khóa sản phẩm. Tôi có thể lấy một nhóm trong số này khi ai đó muốn mua một cái búa, sau đó lặp lại thông qua việc gửi cập nhật (sử dụng id_rev) cho đến khi tôi yêu cầu thành công một (các vé đã yêu cầu trước đó sẽ dẫn đến lỗi cập nhật).

Giảm

function (keys, values, combine) {
    return values.length;
}

Hàm giảm này chỉ đơn giản trả về tổng số inventory_ticketmặt hàng chưa có người nhận , vì vậy bạn có thể cho biết có bao nhiêu "búa" để mua.

Cảnh báo

Giải pháp này thể hiện tổng số khoảng 3,5 phút suy nghĩ cho vấn đề cụ thể mà bạn đã trình bày. Có thể có nhiều cách tốt hơn để làm điều này! Điều đó nói rằng, nó làm giảm đáng kể các cập nhật xung đột và giảm nhu cầu phản hồi xung đột với một bản cập nhật mới. Theo mô hình này, bạn sẽ không có nhiều người dùng cố gắng thay đổi dữ liệu trong mục nhập sản phẩm chính. Trong trường hợp tồi tệ nhất, bạn sẽ có nhiều người dùng cố gắng yêu cầu một vé duy nhất và nếu bạn đã lấy được một số người trong số họ từ chế độ xem của mình, bạn chỉ cần chuyển sang vé tiếp theo và thử lại.

Tham khảo: https://wiki.apache.org/couchdb/Frequently_asked_questions#How_do_I_use_transactions_with_CouchDB.3F


4
Tôi không rõ việc có 'vé' mà bạn cố gắng xác nhận theo trình tự là một cải tiến đáng kể so với việc chỉ cần thử lại đọc / sửa đổi / ghi để cập nhật thực thể chính. Chắc chắn rằng nó có vẻ không đáng phải trả thêm chi phí, đặc biệt nếu bạn có một lượng lớn hàng dự trữ.
Nick Johnson

4
Theo quan điểm của tôi, quy ước vé là "đơn giản hơn" để xây dựng. Cập nhật không thành công đối với mục nhập chính yêu cầu bạn tải lại tài liệu, thực hiện lại thao tác của mình rồi lưu. Điều này cho phép bạn thử và "yêu cầu" một cái gì đó mà không cần phải yêu cầu thêm dữ liệu.
MrKurt

Ngoài ra, nó phụ thuộc vào loại chi phí bạn lo lắng. Bạn sẽ chiến đấu với sự cạnh tranh gia tăng hoặc có các yêu cầu lưu trữ bổ sung. Cho rằng một vé cũng có thể tăng gấp đôi so với hồ sơ mua hàng, tôi không biết rằng sẽ có nhiều vấn đề về lưu trữ như bạn nghĩ.
MrKurt

2
Tôi đang chỉnh sửa trường số lượng của tài liệu sản phẩm. Sau đó, tôi phải tạo hàng nghìn "vé" nếu số lượng = 2K chẳng hạn. Sau đó, tôi giảm một số lượng, tôi phải xóa một số vé. Nghe có vẻ hoàn toàn không liên quan đến tôi. Đau đầu nhiều trong những trường hợp sử dụng cơ bản. Có thể tôi đang thiếu thứ gì đó, nhưng tại sao không khôi phục lại hành vi giao dịch đã xóa trước đó, chỉ cần đặt nó trở thành tùy chọn với một cái gì đó như _bulk_docs? Allow_on_conflict = true. Khá hữu ích trong các cấu hình một cái chính.
Sam

3
@mehaase: Đọc phần này: guide.couchdb.org/draft/recipes.html , câu trả lời tóm gọn trong cơ cấu dữ liệu nội bộ của couchdb "bạn không bao giờ thay đổi dữ liệu, bạn chỉ thêm mới". Trong trường hợp của bạn, điều đó có nghĩa là tạo một giao dịch (nguyên tử) từ tài khoản này sang tài khoản chuyển tiền để ghi nợ và giao dịch thứ hai (nguyên tử) từ tài khoản chuyển tiếp về phía trước (hoặc ngược lại). Đó là cách mà các ngân hàng thực sự làm. Mỗi bước luôn được ghi lại.
Fabian Zeindl

26

Mở rộng câu trả lời của MrKurt. Đối với nhiều trường hợp, bạn không cần phải đổi vé cổ phiếu theo thứ tự. Thay vì chọn vé đầu tiên, bạn có thể chọn ngẫu nhiên từ các vé còn lại. Với một số lượng lớn vé và một số lượng lớn các yêu cầu đồng thời, bạn sẽ giảm bớt sự tranh cãi về những vé đó, so với mọi người đều cố gắng lấy vé đầu tiên.


21

Một mô hình thiết kế cho các giao dịch hoàn chỉnh là tạo ra một "căng thẳng" trong hệ thống. Đối với trường hợp sử dụng ví dụ phổ biến của giao dịch tài khoản ngân hàng, bạn phải đảm bảo cập nhật tổng số cho cả hai tài khoản liên quan:

  • Lập chứng từ giao dịch "chuyển 10 USD từ tài khoản 11223 sang tài khoản 88733". Điều này tạo ra sự căng thẳng trong hệ thống.
  • Để giải quyết mọi căng thẳng quét tất cả các tài liệu giao dịch và
    • Nếu tài khoản nguồn chưa được cập nhật, hãy cập nhật tài khoản nguồn (-10 USD)
    • Nếu tài khoản nguồn đã được cập nhật nhưng tài liệu giao dịch không hiển thị điều này thì hãy cập nhật tài liệu giao dịch (ví dụ: đặt cờ "nguồn gốc" trong tài liệu)
    • Nếu tài khoản mục tiêu chưa được cập nhật, hãy cập nhật tài khoản mục tiêu (+10 USD)
    • Nếu tài khoản đích đã được cập nhật nhưng tài liệu giao dịch không hiển thị điều này thì hãy cập nhật tài liệu giao dịch
    • Nếu cả hai tài khoản đã được cập nhật, bạn có thể xóa tài liệu giao dịch hoặc giữ nó để kiểm tra.

Việc quét căng thẳng nên được thực hiện trong một quy trình phụ trợ cho tất cả "tài liệu căng thẳng" để giữ cho thời gian căng thẳng trong hệ thống ngắn. Trong ví dụ trên sẽ có sự mâu thuẫn được dự đoán trong thời gian ngắn khi tài khoản đầu tiên đã được cập nhật nhưng tài khoản thứ hai vẫn chưa được cập nhật. Điều này phải được tính đến giống như cách bạn sẽ đối phó với tính nhất quán cuối cùng nếu Couchdb của bạn được phân phối.

Một cách triển khai khác có thể tránh hoàn toàn nhu cầu giao dịch: chỉ cần lưu trữ các tài liệu căng thẳng và đánh giá trạng thái hệ thống của bạn bằng cách đánh giá mọi tài liệu căng thẳng liên quan. Trong ví dụ trên, điều này có nghĩa là tổng số cho một tài khoản chỉ được xác định là tổng giá trị trong các tài liệu giao dịch có liên quan đến tài khoản này. Trong Couchdb, bạn có thể lập mô hình này rất độc đáo dưới dạng một bản đồ / chế độ xem thu nhỏ.


5
Nhưng còn những trường hợp tài khoản bị ghi nợ nhưng tài liệu căng thẳng không thay đổi thì sao? Bất kỳ kịch bản thất bại nào giữa hai điểm đó, nếu chúng không phải là nguyên tử, sẽ gây ra sự mâu thuẫn vĩnh viễn, phải không? Một cái gì đó về quy trình phải là nguyên tử, đó là điểm của một giao dịch.
Ian Varley

Vâng, bạn nói đúng, trong trường hợp này - trong khi căng thẳng vẫn chưa được giải quyết - sẽ có sự mâu thuẫn. Tuy nhiên, sự mâu thuẫn chỉ là tạm thời cho đến khi lần quét tài liệu căng thẳng tiếp theo phát hiện ra điều này. Đó là thương hiệu trong trường hợp này, một kiểu nhất quán cuối cùng về thời gian. Miễn là bạn giảm tài khoản nguồn trước và sau đó tăng tài khoản đích, điều này có thể chấp nhận được. Nhưng hãy cẩn thận: các tài liệu căng thẳng sẽ không cung cấp cho bạn các giao dịch ACID trên REST. Nhưng chúng có thể là sự cân bằng tốt giữa REST và ACID tinh khiết.
ordnungswidrig

4
Hãy tưởng tượng mọi tài liệu về căng thẳng đều có dấu thời gian và tài liệu tài khoản có trường 'áp dụng căng thẳng cuối cùng' - hoặc danh sách các căng thẳng được áp dụng. Khi ghi nợ tài khoản nguồn, bạn cũng cập nhật trường 'áp dụng lần cuối'. Hai hoạt động đó là nguyên tử vì chúng nằm trên cùng một tài liệu. Tài khoản đích cũng có một trường tương tự. Bằng cách đó, hệ thống luôn có thể cho biết tài liệu căng thẳng nào đã được áp dụng cho tài khoản nào.
Jesse Hallett

1
Làm thế nào để phát hiện xem tài liệu nguồn / đích đã được cập nhật chưa? Điều gì sẽ xảy ra nếu nó không thành công sau bước 1, sau đó được thực hiện lại và không thành công lần nữa, v.v., bạn sẽ tiếp tục trừ tài khoản nguồn?
wump

1
@wump: bạn sẽ cần phải ghi lại rằng tài liệu căng thẳng đã được áp dụng cho tài khoản. ví dụ: bằng cách thêm id tài liệu căng thẳng vào một thuộc tính danh sách của một trong hai tài khoản. khi tất cả các tài khoản chạm vào tài liệu căng thẳng đã được cập nhật thì hãy đánh dấu tài liệu căng thẳng là "xong" hoặc xóa nó. Sau đó, id tài liệu có thể bị xóa khỏi danh sách cho tất cả các tài khoản.
ordnungswidrig

6

Không, CouchDB thường không thích hợp cho các ứng dụng giao dịch vì nó không hỗ trợ các hoạt động nguyên tử trong môi trường được phân cụm / sao chép.

CouchDB đã hy sinh khả năng giao dịch để có lợi cho khả năng mở rộng. Để có các hoạt động nguyên tử, bạn cần một hệ thống điều phối trung tâm, hệ thống này hạn chế khả năng mở rộng của bạn.

Nếu bạn có thể đảm bảo rằng bạn chỉ có một phiên bản CouchDB hoặc mọi người sửa đổi một tài liệu cụ thể kết nối với cùng một phiên bản CouchDB thì bạn có thể sử dụng hệ thống phát hiện xung đột để tạo một loại nguyên tử bằng cách sử dụng các phương pháp được mô tả ở trên nhưng nếu sau đó bạn mở rộng quy mô thành một cụm hoặc sử dụng một dịch vụ được lưu trữ như Cloudant, nó sẽ bị hỏng và bạn sẽ phải làm lại phần đó của hệ thống.

Vì vậy, đề xuất của tôi là sử dụng thứ gì đó khác ngoài CouchDB cho số dư tài khoản của bạn, theo cách đó sẽ dễ dàng hơn nhiều.


5

Để giải quyết vấn đề của OP, Couch có lẽ không phải là lựa chọn tốt nhất ở đây. Sử dụng chế độ xem là một cách tuyệt vời để theo dõi hàng tồn kho, nhưng việc kẹp về 0 là điều không thể. Vấn đề là điều kiện cuộc đua khi bạn đọc kết quả của một lượt xem, quyết định rằng bạn có thể sử dụng vật phẩm "hammer-1", và sau đó viết một tài liệu để sử dụng nó. Vấn đề là không có cách nguyên tử nào để chỉ viết tài liệu để sử dụng cái búa nếu kết quả của chế độ xem là có> 0 cái búa. Nếu 100 người dùng truy vấn chế độ xem cùng lúc và thấy 1 cây búa-1, tất cả họ có thể viết tài liệu để sử dụng cây búa 1, dẫn đến -99 cây búa 1. Trong thực tế, điều kiện chạy đua sẽ khá nhỏ - thực sự nhỏ nếu DB của bạn đang chạy localhost. Nhưng một khi bạn mở rộng quy mô và có một máy chủ hoặc cụm DB ngoài trang web, vấn đề sẽ trở nên đáng chú ý hơn nhiều.

Bản cập nhật cho phản hồi của MrKurt (nó có thể chỉ là ngày tháng hoặc anh ấy có thể không biết về một số tính năng của CouchDB)

Chế độ xem là một cách tốt để xử lý những thứ như số dư / hàng tồn kho trong CouchDB.

Bạn không cần phải phát ra docid và rev trong một dạng xem. Bạn nhận được cả hai thứ đó miễn phí khi bạn truy xuất kết quả xem. Việc phát ra chúng - đặc biệt là ở định dạng dài dòng như từ điển - sẽ chỉ làm cho chế độ xem của bạn lớn hơn một cách không cần thiết.

Một chế độ xem đơn giản để theo dõi số dư khoảng không quảng cáo sẽ trông giống như thế này (cũng nằm ngoài đầu của tôi)

function( doc )
{
    if( doc.InventoryChange != undefined ) {
        for( product_key in doc.InventoryChange ) {
            emit( product_key, 1 );
        }
    }
}

Và chức năng giảm thậm chí còn đơn giản hơn

_sum

Điều này sử dụng một chức năng giảm được tích hợp sẵn chỉ tính tổng các giá trị của tất cả các hàng có các khóa phù hợp.

Trong chế độ xem này, bất kỳ tài liệu nào cũng có thể có thành viên "InventoryChange" ánh xạ product_key's với sự thay đổi trong tổng khoảng không quảng cáo của chúng. I E.

{
    "_id": "abc123",
    "InventoryChange": {
         "hammer_1234": 10,
         "saw_4321": 25
     }
}

Sẽ thêm 10 chiếc búa_1234 và 25 chiếc saw_4321.

{
    "_id": "def456",
    "InventoryChange": {
        "hammer_1234": -5
    }
}

Sẽ đốt 5 búa từ kho.

Với mô hình này, bạn không bao giờ cập nhật bất kỳ dữ liệu nào, chỉ bổ sung. Điều này có nghĩa là không có cơ hội cho xung đột cập nhật. Tất cả các vấn đề giao dịch về cập nhật dữ liệu sẽ biến mất :)

Một điều thú vị khác về mô hình này là BẤT KỲ tài liệu nào trong DB đều có thể cộng và trừ các mục từ khoảng không quảng cáo. Những tài liệu này có thể có tất cả các loại dữ liệu khác trong đó. Bạn có thể có tài liệu "Lô hàng" với một loạt dữ liệu về ngày và giờ nhận, kho hàng, nhân viên nhận hàng, v.v. và miễn là tài liệu đó xác định InventoryChange, tài liệu đó sẽ cập nhật khoảng không quảng cáo. Cũng như tài liệu "Bán", và tài liệu "DamagedItem", v.v. Nhìn vào từng tài liệu, họ đọc rất rõ ràng. Và chế độ xem xử lý tất cả các công việc khó khăn.


Chiến lược thú vị. Là một người mới của CouchDB, có vẻ như để tính toán số lượng búa hiện tại, bạn cần thực hiện lập bản đồ / giảm bớt trong toàn bộ lịch sử thay đổi hàng tồn kho của công ty đối với búa. Đây có thể là năm thay đổi đáng giá. Có một số tính năng tích hợp của CouchDB sẽ làm cho nó hoạt động tốt không?
chadrik

Vâng, các lượt xem trong CouchDB giống như một bản đồ liên tục, liên tục bản đồ / thu nhỏ. Bạn đúng rằng để làm điều đó bắt đầu từ đầu trên một tập dữ liệu lớn sẽ mất nhiều thời gian, nhưng khi các tài liệu mới được thêm vào, chúng chỉ cập nhật chế độ xem hiện có, nó không phải tính toán lại toàn bộ chế độ xem. Hãy nhớ rằng có cả yêu cầu về không gian và CPU cho các khung nhìn. Ngoài ra, ít nhất là khi tôi làm việc với CouchDB một cách chuyên nghiệp (đã được vài năm), điều khá quan trọng là chỉ sử dụng các chức năng giảm được tích hợp sẵn. _Tổng. Javascript tùy chỉnh làm giảm chức năng là rất chậm
wallacer

3

Trên thực tế, bạn có thể theo một cách nào đó. Hãy xem API tài liệu HTTP và cuộn xuống tiêu đề "Sửa đổi nhiều tài liệu với một yêu cầu duy nhất".

Về cơ bản, bạn có thể tạo / cập nhật / xóa một loạt tài liệu trong một yêu cầu đăng bài tới URI / {dbname} / _ Bul_docs và chúng sẽ thành công hoặc không thành công. Tuy nhiên, tài liệu cảnh báo rằng hành vi này có thể thay đổi trong tương lai.

CHỈNH SỬA: Theo dự đoán, từ phiên bản 0.9, tài liệu số lượng lớn không còn hoạt động theo cách này.


Điều đó sẽ không thực sự hữu ích trong tình huống đang được thảo luận, tức là tranh cãi về tài liệu đơn lẻ từ nhiều người dùng.
Kerr

3
Bắt đầu với CouchDB 0.9, ngữ nghĩa của các bản cập nhật hàng loạt đã thay đổi.
Barry Wark

0

Chỉ cần sử dụng loại giải pháp nhẹ SQlite cho các giao dịch và khi giao dịch hoàn tất, hãy nhân bản nó thành công và đánh dấu nó đã sao chép trong SQLite

Bảng SQLite

txn_id    , txn_attribute1, txn_attribute2,......,txn_status
dhwdhwu$sg1   x                    y               added/replicated

Bạn cũng có thể xóa các giao dịch được sao chép thành công.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.