Lưu trữ một danh sách có thể sắp xếp lại trong cơ sở dữ liệu


54

Tôi đang làm việc trên một hệ thống danh sách mong muốn, nơi người dùng có thể thêm các mục vào danh sách mong muốn khác nhau của họ và tôi dự định cho phép người dùng đặt hàng lại các mục sau này. Tôi không thực sự chắc chắn về cách tốt nhất để lưu trữ dữ liệu này trong cơ sở dữ liệu trong khi vẫn nhanh và không chuyển sang một mớ hỗn độn (ứng dụng này sẽ được sử dụng bởi một cơ sở người dùng khá lớn, vì vậy tôi không muốn nó bị hỏng để dọn dẹp các công cụ).

Ban đầu tôi đã thử một positioncột, nhưng có vẻ như điều đó sẽ không hiệu quả khi phải thay đổi giá trị vị trí của mọi mặt hàng khác khi bạn di chuyển chúng.

Tôi đã thấy mọi người sử dụng tự tham chiếu để tham chiếu giá trị trước đó (hoặc tiếp theo), nhưng một lần nữa, có vẻ như bạn sẽ phải cập nhật rất nhiều mục khác trong danh sách.

Một giải pháp khác tôi từng thấy là sử dụng số thập phân và chỉ dán các mục vào khoảng trống giữa chúng, có vẻ như là giải pháp tốt nhất cho đến nay, nhưng tôi chắc chắn phải có cách tốt hơn.

Tôi có thể nói rằng một danh sách điển hình sẽ chứa tối đa khoảng 20 mặt hàng và tôi có thể sẽ giới hạn ở mức 50. Việc đặt hàng lại sẽ sử dụng kéo và thả và có thể sẽ được thực hiện theo đợt để ngăn chặn điều kiện cuộc đua và như vậy yêu cầu ajax. Tôi đang sử dụng postgres (trên heroku) nếu nó quan trọng.

Có ai có ý tưởng nào?

Chúc mừng cho bất kỳ sự giúp đỡ!


Bạn có thể làm một chút điểm chuẩn và cho chúng tôi biết IO hoặc Cơ sở dữ liệu sẽ là một nút cổ chai không?
rwong

Câu hỏi liên quan về stackoverflow .
Jordão

Với tính năng tự tham khảo, khi di chuyển một mục từ một nơi trong danh sách sang nơi khác, bạn chỉ phải cập nhật 2 mục. Xem en.wikipedia.org/wiki/Linked_list
Pieter B

Hmm, không chắc tại sao các danh sách được liên kết hầu như không nhận được bất kỳ sự chú ý nào trong các câu trả lời.
Christiaan Westerbeek

Câu trả lời:


32

Đầu tiên, đừng cố làm bất cứ điều gì thông minh bằng số thập phân, vì chúng sẽ khiến bạn khó chịu. REALDOUBLE PRECISIONkhông chính xác và có thể không thể hiện đúng những gì bạn đưa vào chúng. NUMERIClà chính xác, nhưng trình tự di chuyển đúng sẽ khiến bạn không chính xác và việc thực hiện của bạn sẽ bị hỏng.

Hạn chế di chuyển đến những thăng trầm đơn lẻ làm cho toàn bộ hoạt động rất dễ dàng. Đối với danh sách các mục được đánh số liên tục, bạn có thể di chuyển một mục lên bằng cách giảm vị trí của nó và tăng số vị trí của bất cứ thứ gì mà phần giảm trước đó đưa ra. (Nói cách khác, vật phẩm 5sẽ trở thành 4và vật phẩm 4trở thành 5, thực sự là một sự hoán đổi như Morons đã mô tả trong câu trả lời của anh ta.) Di chuyển nó xuống sẽ ngược lại. Lập chỉ mục bảng của bạn bằng bất cứ điều gì xác định duy nhất một danh sách và vị trí và bạn có thể làm điều đó với hai UPDATEgiây trong một giao dịch sẽ chạy rất nhanh. Trừ khi người dùng của bạn sắp xếp lại danh sách của họ với tốc độ siêu phàm, điều này sẽ không gây ra nhiều tải.

Di chuyển kéo và thả (ví dụ: di chuyển vật phẩm 6để ngồi giữa các vật phẩm 910) phức tạp hơn một chút và phải được thực hiện khác nhau tùy thuộc vào vị trí mới ở trên hay dưới vị trí cũ. Trong ví dụ trên, bạn phải mở một lỗ hổng bằng cách tăng tất cả các vị trí lớn hơn 9, cập nhật 6vị trí của vật phẩm thành mới 10và sau đó giảm vị trí của mọi thứ lớn hơn 6để điền vào vị trí trống. Với cùng một chỉ mục tôi đã mô tả trước đây, điều này sẽ nhanh chóng. Bạn thực sự có thể thực hiện việc này nhanh hơn một chút so với tôi mô tả bằng cách giảm thiểu số lượng hàng mà giao dịch chạm vào, nhưng đó là một sự tối ưu hóa vi mô mà bạn không cần cho đến khi bạn có thể chứng minh được sự tắc nghẽn.

Dù bằng cách nào, việc cố gắng vượt qua cơ sở dữ liệu bằng một giải pháp pha chế quá thông minh, quá thông minh thường không dẫn đến thành công. Cơ sở dữ liệu đáng muối của họ đã được viết cẩn thận để thực hiện các hoạt động này rất, rất nhanh bởi những người rất, rất giỏi về nó.


Đây chính xác là cách tôi xử lý nó trong một hệ thống chuẩn bị đấu thầu dự án mà chúng tôi đã có từ lâu năm trước. Ngay cả trong Access, bản cập nhật cũng được phân chia nhanh chóng.
HLGEM

Cảm ơn đã khám phá, Blrfl! Tôi đã cố gắng thực hiện tùy chọn thứ hai, nhưng tôi thấy rằng nếu tôi xóa các mục từ giữa danh sách, nó sẽ để lại các khoảng trống trong các vị trí (đó là một triển khai khá ngây thơ). Có cách nào dễ dàng để tránh tạo ra những khoảng trống như thế này không, hoặc tôi sẽ phải thực hiện thủ công mỗi lần tôi đặt hàng lại thứ gì đó (nếu tôi phải thực sự quản lý nó)?
Tom Brunoli

2
@TomBrunoli: Tôi phải suy nghĩ về việc triển khai một chút trước khi nói chắc chắn, nhưng bạn có thể có thể loại bỏ hầu hết hoặc tất cả việc đánh số lại tự động bằng các kích hoạt. Ví dụ: nếu bạn xóa mục 7, trình kích hoạt sẽ giảm tất cả các hàng trong cùng một danh sách được đánh số lớn hơn 7 sau khi việc xóa diễn ra. Chèn sẽ làm điều tương tự (chèn một mục 7 sẽ tăng tất cả các hàng 7 hoặc cao hơn). Việc kích hoạt một bản cập nhật (ví dụ, di chuyển mục 3 trong khoảng từ 9 đến 10) sẽ phức tạp hơn vừa phải nhưng chắc chắn là trong phạm vi có thể thực hiện được.
Blrfl

Tôi đã không thực sự xem xét các yếu tố kích hoạt trước đây nhưng đó có vẻ là một cách tốt để làm điều đó.
Tom Brunoli

1
@TomBrunoli: Đối với tôi, việc sử dụng các kích hoạt để làm điều này có thể gây ra các tầng. Các thủ tục được lưu trữ với tất cả các thay đổi trong giao dịch có thể là con đường tốt hơn cho việc này.
Blrfl

15

Câu trả lời tương tự từ đây https://stackoverflow.com/a/49956113/10608


Giải pháp: tạo indexmột chuỗi (vì về bản chất, các chuỗi có "độ chính xác tùy ý" vô hạn). Hoặc nếu bạn sử dụng int, tăng thêm index100 thay vì 1.

Vấn đề hiệu năng là thế này: không có giá trị "ở giữa" giữa hai mục được sắp xếp.

item      index
-----------------
gizmo     1
              <<------ Oh no! no room between 1 and 2.
                       This requires incrementing _every_ item after it
gadget    2
gear      3
toolkit   4
box       5

Thay vào đó, hãy làm như thế này (giải pháp tốt hơn bên dưới):

item      index
-----------------
gizmo     100
              <<------ Sweet :). I can re-order 99 (!) items here
                       without having to change anything else
gadget    200
gear      300
toolkit   400
box       500

Thậm chí tốt hơn: đây là cách Jira giải quyết vấn đề này. "Thứ hạng" của chúng (thứ bạn gọi là chỉ số) là một giá trị chuỗi cho phép một tấn phòng thở ở giữa các mục được xếp hạng.

Đây là một ví dụ thực tế về cơ sở dữ liệu jira tôi làm việc với

   id    | jira_rank
---------+------------
 AP-2405 | 0|hzztxk:
 ES-213  | 0|hzztxs:
 AP-2660 | 0|hzztzc:
 AP-2688 | 0|hzztzk:
 AP-2643 | 0|hzztzs:
 AP-2208 | 0|hzztzw:
 AP-2700 | 0|hzztzy:
 AP-2702 | 0|hzztzz:
 AP-2411 | 0|hzztzz:i
 AP-2440 | 0|hzztzz:r

Lưu ý ví dụ này hzztzz:i. Ưu điểm của xếp hạng chuỗi là bạn hết phòng giữa hai mục, bạn vẫn không phải xếp hạng lại bất cứ thứ gì khác. Bạn chỉ cần bắt đầu nối thêm các ký tự vào chuỗi để thu hẹp trọng tâm.


1
Tôi đã cố gắng đưa ra một số cách để làm điều này bằng cách chỉ cập nhật một bản ghi duy nhất, và câu trả lời này giải thích giải pháp mà tôi đã nghĩ ra trong đầu rất tốt.
NSjonas

13

Tôi đã thấy mọi người sử dụng tự tham chiếu để tham chiếu giá trị trước đó (hoặc tiếp theo), nhưng một lần nữa, có vẻ như bạn sẽ phải cập nhật rất nhiều mục khác trong danh sách.

Tại sao? Giả sử bạn thực hiện một cách tiếp cận bảng danh sách được liên kết với các cột (listID, itemID, nextItemID).

Chèn một mục mới vào danh sách sẽ tốn một lần chèn và một hàng được sửa đổi.

Định vị lại một mục có chi phí sửa đổi ba hàng (mục được di chuyển, mục trước mục đó và mục trước vị trí mới của nó).

Loại bỏ một mục chi phí một xóa và một hàng sửa đổi.

Các chi phí này vẫn giữ nguyên cho dù danh sách có 10 mặt hàng hay 10.000 mặt hàng. Trong cả ba trường hợp, có một sửa đổi ít hơn nếu hàng đích là mục danh sách đầu tiên. Nếu bạn thường xuyên hoạt động trên mục danh sách cuối cùng , có thể có ích khi lưu trữ thuận lợi hơn là tiếp theo.


10

"nhưng có vẻ như điều đó sẽ không hiệu quả"

Bạn đã đo lường điều đó? Hay đó chỉ là một phỏng đoán? Đừng đưa ra những giả định như vậy mà không có bất kỳ bằng chứng nào.

"20 đến 50 mục trên mỗi danh sách"

Thành thật mà nói, đó không phải là "toàn bộ nhiều mặt hàng", với tôi nghe có vẻ rất ít.

Tôi khuyên bạn nên tuân theo cách tiếp cận "cột vị trí" (nếu đó là cách thực hiện đơn giản nhất cho bạn). Đối với kích thước danh sách nhỏ như vậy, đừng bắt đầu tối ưu hóa không cần thiết trước khi bạn gặp vấn đề về hiệu suất thực sự


6

Đây thực sự là một câu hỏi về quy mô, và trường hợp sử dụng ..

Có bao nhiêu mặt hàng bạn mong đợi trong một danh sách? Nếu hàng triệu người, tôi nghĩ rằng chiêng thập phân là con đường rõ ràng.

Nếu 6 thì số nguyên đánh số lại là sự lựa chọn rõ ràng. s Ngoài ra các câu hỏi là làm thế nào các danh sách hoặc sắp xếp lại. Nếu bạn đang sử dụng một mũi tên lên và xuống (di chuyển lên hoặc xuống một khe tại một thời điểm), thì tôi sẽ sử dụng các số nguyên sau đó trao đổi với trước (hoặc tiếp theo) khi di chuyển.

Ngoài ra, tần suất bạn cam kết là bao nhiêu, nếu người dùng có thể thực hiện 250 thay đổi thì hãy cam kết cùng một lúc, hơn là tôi nói số nguyên với việc đánh số lại ...

tl; dr: Cần thêm thông tin.


Chỉnh sửa: "Danh sách mong muốn" nghe có vẻ như rất nhiều danh sách nhỏ (giả định, điều này có thể sai) .. Vì vậy, tôi nói Integer với việc đánh số lại. (Mỗi danh sách chứa Postion riêng của nó)


Tôi sẽ cập nhật câu hỏi với một số bối cảnh khác
Tom Brunoli

số thập phân không hoạt động, vì độ chính xác bị giới hạn và mỗi mục được chèn có thể mất 1 bit
njzk2

3

Nếu mục tiêu là để giảm thiểu số lượng hoạt động cơ sở dữ liệu trên mỗi hoạt động sắp xếp lại:

Giả sử rằng

  • Tất cả các mặt hàng mua sắm có thể được liệt kê với số nguyên 32 bit.
  • Có giới hạn kích thước tối đa cho danh sách mong muốn của người dùng. (Tôi thấy một số trang web phổ biến sử dụng giới hạn 20 - 40 mục)

Lưu trữ danh sách mong muốn được sắp xếp của người dùng dưới dạng một chuỗi các số nguyên (mảng số nguyên) trong một cột. Mỗi khi danh sách mong muốn được sắp xếp lại, toàn bộ mảng (hàng đơn; cột đơn) được cập nhật - sẽ được thực hiện với một bản cập nhật SQL duy nhất.

https://www.postgresql.org/docs/cản/static/arrays.html


Nếu mục tiêu là khác nhau, hãy gắn bó với phương pháp "cột vị trí".


Về "tốc độ", đảm bảo điểm chuẩn phương pháp thủ tục được lưu trữ. Mặc dù việc phát hành hơn 20 bản cập nhật riêng biệt cho một lần xáo trộn danh sách mong muốn có thể bị chậm, nhưng có thể có một cách nhanh chóng bằng cách sử dụng thủ tục được lưu trữ.


3

OK Tôi phải đối mặt với vấn đề khó khăn này gần đây, và tất cả các câu trả lời trong bài hỏi và trả lời này đã mang lại nhiều cảm hứng. Theo tôi thấy, mỗi giải pháp đều có ưu và nhược điểm.

  • Nếu positiontrường phải tuần tự mà không có khoảng trống, thì về cơ bản bạn sẽ cần phải sắp xếp lại toàn bộ danh sách. Đây là một hoạt động O (N). Ưu điểm là phía khách hàng sẽ không cần bất kỳ logic đặc biệt nào để có được đơn đặt hàng.

  • Nếu chúng ta muốn tránh hoạt động O (N) NHƯNG VẪN duy trì một chuỗi chính xác, một trong những cách tiếp cận là sử dụng "tự tham chiếu để tham chiếu giá trị trước đó (hoặc tiếp theo)". Đây là một kịch bản danh sách liên kết sách giáo khoa. Theo thiết kế, nó sẽ KHÔNG phát sinh "toàn bộ các mặt hàng khác trong danh sách". Tuy nhiên, điều này đòi hỏi phía khách hàng (một dịch vụ web hoặc có lẽ là một ứng dụng di động) để thực hiện logic ba bên danh sách liên kết để rút ra thứ tự.

  • Một số biến thể không sử dụng tham chiếu tức là danh sách liên kết. Họ chọn thể hiện toàn bộ đơn hàng dưới dạng một blob khép kín, chẳng hạn như một chuỗi JSON-in-a-string [5,2,1,3,...]; thứ tự như vậy sau đó sẽ được lưu trữ ở một nơi tách biệt. Cách tiếp cận này cũng có tác dụng phụ là yêu cầu mã phía máy khách để duy trì blob thứ tự riêng biệt đó.

  • Trong nhiều trường hợp, chúng tôi không thực sự cần lưu trữ thứ tự chính xác, chúng tôi chỉ cần duy trì thứ hạng tương đối trong mỗi bản ghi. Do đó, chúng tôi có thể cho phép khoảng cách giữa các bản ghi tuần tự. Biến thể bao gồm: (1) sử dụng số nguyên với các khoảng trống như 100, 200, 300 ... nhưng bạn sẽ nhanh chóng hết khoảng trống và sau đó cần quá trình khôi phục; (2) sử dụng số thập phân đi kèm với các khoảng trống tự nhiên, nhưng bạn sẽ cần phải quyết định xem bạn có thể sống với giới hạn chính xác cuối cùng hay không; (3) sử dụng xếp hạng dựa trên chuỗi như được mô tả trong câu trả lời này, nhưng hãy cẩn thận các bẫy triển khai khó khăn .

  • Câu trả lời thực sự có thể là "nó phụ thuộc". Xem lại yêu cầu kinh doanh của bạn. Ví dụ: nếu đó là một hệ thống danh sách mong muốn, cá nhân tôi sẽ vui vẻ sử dụng một hệ thống được tổ chức chỉ bằng một vài cấp bậc là "phải có", "tốt để có", "có thể sau này", và sau đó trình bày các mặt hàng mà không cần cụ thể thứ tự bên trong mỗi cấp bậc. Nếu đó là một hệ thống phân phối, bạn rất có thể sử dụng thời gian giao hàng như một thứ hạng thô đi kèm với khoảng cách tự nhiên (và phòng ngừa xung đột tự nhiên vì không có giao hàng nào xảy ra cùng một lúc). Số dặm của bạn có thể thay đổi.


2

Sử dụng số dấu phẩy động cho cột vị trí.

Sau đó, bạn có thể sắp xếp lại danh sách chỉ thay đổi cột vị trí trong hàng "đã di chuyển".

Về cơ bản nếu người dùng của bạn muốn định vị "đỏ" sau "xanh" nhưng trước "vàng"

Sau đó, bạn chỉ cần tính toán

red.position = ((yellow.position - blue.position) / 2) + blue.position

Sau vài triệu vị trí lại, bạn có thể nhận được số dấu phẩy động nhỏ đến mức không có "giữa" - nhưng điều này gần giống như nhìn thấy một con kỳ lân.

Bạn có thể thực hiện điều này bằng cách sử dụng một trường số nguyên với khoảng cách ban đầu là 1000. Vì vậy, oredring intial của bạn sẽ là 1000-> blue, 2000-> Yellow, 3000-> Red. Sau khi "di chuyển" Màu đỏ sau màu xanh, bạn sẽ có 1000-> màu xanh lam, 1500-> Đỏ, 2000-> Vàng.

Vấn đề là với khoảng cách ban đầu dường như lớn là 1000, chỉ cần 10 lần di chuyển sẽ khiến bạn rơi vào tình huống như 1000-> blue, 1001-puce, 1004-> biege ...... nơi bạn sẽ không còn có thể để chèn bất cứ thứ gì sau "màu xanh" mà không đánh số lại toàn bộ danh sách. Sử dụng số dấu phẩy động sẽ luôn có một điểm "nửa chừng" giữa hai vị trí.


4
Lập chỉ mục và sắp xếp trong một cơ sở dữ liệu dựa trên float là đắt hơn ints. Ints cũng là một loại thứ tự đẹp ... không cần phải gửi dưới dạng bit để có thể được sắp xếp trên máy khách (sự khác biệt giữa hai số hiển thị giống nhau khi được in, nhưng có giá trị bit khác nhau).

Nhưng bất kỳ lược đồ nào sử dụng ints có nghĩa là bạn cần cập nhật tất cả / hầu hết các hàng trong danh sách mỗi khi đơn hàng thay đổi. Sử dụng phao bạn chỉ cập nhật hàng đã di chuyển. Ngoài ra "nổi nhiều hơn ints" rất nhiều phụ thuộc vào việc triển khai và phần cứng được sử dụng. Chắc chắn cpu bổ sung có liên quan là không đáng kể so với cpu cần thiết để cập nhật một hàng và các chỉ mục liên quan của nó.
James Anderson

5
Đối với những người không tán thành, giải pháp này chính xác là những gì Trello ( trello.com ) làm. Mở trình gỡ lỗi chrome của bạn và tìm đầu ra json từ trước / sau khi sắp xếp lại (kéo / thả thẻ) và bạn nhận được - "pos": 1310719, + "pos": 638975.5. Công bằng mà nói, hầu hết mọi người không làm danh sách trello với 4 triệu mục trong đó, nhưng kích thước danh sách và trường hợp sử dụng của Trello khá phổ biến đối với nội dung có thể sắp xếp của người dùng. Và bất cứ thứ gì có thể phân loại người dùng đều không liên quan gì đến hiệu năng cao, tốc độ sắp xếp int vs float là điều cần thiết, đặc biệt là việc xem xét cơ sở dữ liệu chủ yếu bị hạn chế bởi hiệu suất IO.
zelk

1
@PieterB Còn về 'tại sao không sử dụng số nguyên 64 bit', tôi chủ yếu nói về công thái học cho nhà phát triển. Có độ sâu xấp xỉ <1.0 khi có> 1.0 cho số float trung bình của bạn, vì vậy bạn có thể mặc định cột 'vị trí' thành 1.0 và chèn 0,5, 0,25, 0,75 dễ dàng như nhân đôi. Với số nguyên, mặc định của bạn sẽ phải là 2 ^ 30 hoặc hơn, khiến bạn hơi khó nghĩ khi bạn gỡ lỗi. Là 4073741824 lớn hơn 496359787? Bắt đầu đếm chữ số.
zelk

1
Hơn nữa, nếu bạn từng gặp phải trường hợp bạn hết dung lượng giữa các số ... thì không khó để sửa. Di chuyển một trong số họ. Nhưng điều quan trọng là nó hoạt động theo cách nỗ lực nhất, xử lý nhiều chỉnh sửa đồng thời của các bên khác nhau (ví dụ trello). Bạn có thể chia hai số, thậm chí có thể rắc một chút tiếng ồn ngẫu nhiên và voila, ngay cả khi người khác làm điều tương tự cùng một lúc vẫn có một lệnh toàn cầu và bạn không cần phải XÁC NHẬN trong giao dịch để có được ở đó
zelk
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.