Cách triển khai hệ thống thẻ


90

Tôi đã tự hỏi cách tốt nhất là triển khai hệ thống thẻ, như hệ thống được sử dụng trên SO. Tôi đã nghĩ đến điều này nhưng tôi không thể đưa ra một giải pháp tốt có thể mở rộng.

Tôi đã nghĩ đến việc có một giải pháp 3 bảng cơ bản: có một tagsbảng, một articlesbảng và một tag_to_articlesbảng.

Đây có phải là giải pháp tốt nhất cho vấn đề này không hay có những lựa chọn thay thế? Sử dụng phương pháp này, bảng sẽ cực kỳ lớn theo thời gian, và tôi cho rằng việc tìm kiếm này không quá hiệu quả. Mặt khác, điều quan trọng không phải là truy vấn thực thi nhanh.


Câu trả lời:


119

Tôi tin rằng bạn sẽ thấy thú vị với bài đăng blog này: Tags: Các lược đồ cơ sở dữ liệu

Vấn đề: Bạn muốn có một lược đồ cơ sở dữ liệu nơi bạn có thể gắn thẻ dấu trang (hoặc một bài đăng trên blog hoặc bất cứ thứ gì) với bao nhiêu thẻ tùy thích. Sau đó, bạn muốn chạy các truy vấn để ràng buộc các dấu trang vào một tổ hợp hoặc giao điểm của các thẻ. Bạn cũng muốn loại trừ (giả sử: trừ) một số thẻ khỏi kết quả tìm kiếm.

Giải pháp “MySQLicious”

Trong giải pháp này, lược đồ chỉ có một bảng, nó không được chuẩn hóa. Loại này được gọi là “giải pháp MySQLicious” vì MySQLicious nhập dữ liệu del.icio.us vào một bảng có cấu trúc này.

nhập mô tả hình ảnh ở đâynhập mô tả hình ảnh ở đây

Truy vấn Intersection (AND) cho “search + webservice + semweb”:

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags LIKE "%semweb%"

Union (HOẶC) Truy vấn cho “search | webservice | semweb”:

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
OR tags LIKE "%webservice%"
OR tags LIKE "%semweb%"

Truy vấn trừ cho “search + webservice-semweb”

SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags NOT LIKE "%semweb%"

Giải pháp "Scuttle"

Scuttle sắp xếp dữ liệu của nó trong hai bảng. Bảng “scCategories” đó là bảng “thẻ” và có khóa ngoại cho bảng “đánh dấu”.

nhập mô tả hình ảnh ở đây

Truy vấn Intersection (AND) cho “bookmark + webservice + semweb”:

SELECT b.*
FROM scBookmarks b, scCategories c
WHERE c.bId = b.bId
AND (c.category IN ('bookmark', 'webservice', 'semweb'))
GROUP BY b.bId
HAVING COUNT( b.bId )=3

Đầu tiên, tất cả các tổ hợp thẻ dấu trang đều được tìm kiếm, trong đó thẻ là “dấu trang”, “dịch vụ web” hoặc “semweb” (c.category IN ('dấu trang', 'dịch vụ web', 'semweb')), sau đó chỉ là dấu trang đã tính đến tất cả ba thẻ được tìm kiếm (HAVING COUNT (b.bId) = 3).

Union (HOẶC) Truy vấn cho “bookmark | webservice | semweb”: Chỉ cần bỏ đi mệnh đề HAVING và bạn đã có union:

SELECT b.*
FROM scBookmarks b, scCategories c
WHERE c.bId = b.bId
AND (c.category IN ('bookmark', 'webservice', 'semweb'))
GROUP BY b.bId

Trừ (Loại trừ) Truy vấn cho “bookmark + webservice-semweb”, nghĩa là: bookmark VÀ webservice VÀ KHÔNG phải semweb.

SELECT b. *
FROM scBookmarks b, scCategories c
WHERE b.bId = c.bId
AND (c.category IN ('bookmark', 'webservice'))
AND b.bId NOT
IN (SELECT b.bId FROM scBookmarks b, scCategories c WHERE b.bId = c.bId AND c.category = 'semweb')
GROUP BY b.bId
HAVING COUNT( b.bId ) =2

Bỏ đi COUNTING COUNT sẽ dẫn đến Truy vấn cho “bookmark | webservice-semweb”.


Giải pháp "Toxi"

Toxi đã đưa ra cấu trúc ba bảng. Qua bảng “sơ đồ thẻ”, các dấu trang và các thẻ có liên quan n-to-m. Mỗi thẻ có thể được sử dụng cùng với các dấu trang khác nhau và ngược lại. Lược đồ DB này cũng được sử dụng bởi wordpress. Các truy vấn khá giống như trong giải pháp "scuttle".

nhập mô tả hình ảnh ở đây

Truy vấn Intersection (AND) cho “bookmark + webservice + semweb”

SELECT b.*
FROM tagmap bt, bookmark b, tag t
WHERE bt.tag_id = t.tag_id
AND (t.name IN ('bookmark', 'webservice', 'semweb'))
AND b.id = bt.bookmark_id
GROUP BY b.id
HAVING COUNT( b.id )=3

Union (HOẶC) Truy vấn cho “bookmark | webservice | semweb”

SELECT b.*
FROM tagmap bt, bookmark b, tag t
WHERE bt.tag_id = t.tag_id
AND (t.name IN ('bookmark', 'webservice', 'semweb'))
AND b.id = bt.bookmark_id
GROUP BY b.id

Trừ (Loại trừ) Truy vấn cho “bookmark + webservice-semweb”, nghĩa là: bookmark VÀ webservice VÀ KHÔNG phải semweb.

SELECT b. *
FROM bookmark b, tagmap bt, tag t
WHERE b.id = bt.bookmark_id
AND bt.tag_id = t.tag_id
AND (t.name IN ('Programming', 'Algorithms'))
AND b.id NOT IN (SELECT b.id FROM bookmark b, tagmap bt, tag t WHERE b.id = bt.bookmark_id AND bt.tag_id = t.tag_id AND t.name = 'Python')
GROUP BY b.id
HAVING COUNT( b.id ) =2

Bỏ đi COUNTING COUNT sẽ dẫn đến Truy vấn cho “bookmark | webservice-semweb”.


3
tác giả của bài đăng trên blog đó ở đây. Blog không còn bị chặn bởi Chrome (lỗ hổng wordpress ngu ngốc, đã chuyển sang tumblr ngay bây giờ). Kudos cho biến nó thành markdown
hansaplast

chào bạn @Philipp. OK, đã chỉnh sửa câu trả lời của tôi. BTW, cảm ơn vì bài đăng tuyệt vời trên hệ thống thẻ cơ sở dữ liệu.
Nick Dandoulakis

1
Cũng như một lưu ý: Nếu bạn muốn Truy vấn Giao lộ cho giải pháp Toxi cũng hiển thị dấu trang nếu bạn tìm kiếm 'dấu trang' VÀ 'dịch vụ web', bạn sẽ cần thay đổi "HAVING COUNT (b.id) = 3" từ 3 thành "sizeof (array ('bookmark', 'webservice'))". Chỉ là một chi tiết nhỏ nếu bạn có kế hoạch sử dụng điều này làm chức năng truy vấn thẻ động.
độc hại 20

3
bất kỳ liên kết nào để so sánh hiệu suất cho các giải pháp khác nhau được đề cập trong bài đăng?
kampta

@kampta, không, tôi không có bất kỳ liên kết nào.
Nick Dandoulakis

8

Không có gì sai với giải pháp ba bảng của bạn.

Một tùy chọn khác là giới hạn số lượng thẻ có thể được áp dụng cho một bài báo (như 5 trong SO) và thêm chúng trực tiếp vào bảng bài viết của bạn.

Bình thường hóa DB có những lợi ích và hạn chế của nó, giống như việc nối các thứ cứng vào một bảng cũng có những lợi ích và hạn chế.

Không có gì nói rằng bạn không thể làm cả hai. Nó đi ngược lại với các mô hình DB quan hệ để lặp lại thông tin, nhưng nếu mục tiêu là hiệu suất, bạn có thể phải phá vỡ các mô hình.


Có, đặt các thẻ trực tiếp vào bảng bài viết chắc chắn sẽ là một lựa chọn, mặc dù có một số hạn chế đối với phương pháp này. Nếu bạn lưu trữ 5 thẻ trong một trường được phân tách bằng dấu phẩy như (tag1,2,3,4), đây sẽ là một phương pháp dễ dàng. Câu hỏi là nếu việc tìm kiếm sẽ diễn ra nhanh hơn. Ví dụ, ai đó muốn xem mọi thứ với tag1, bạn phải tìm toàn bộ bảng bài viết. Điều này sẽ ít hơn so với bảng tag_to_article. Nhưng một lần nữa, bảng tags_to_article lại mỏng hơn. Một điều nữa là bạn phải nổ tung mọi lúc trong php, tôi không biết việc này có mất thời gian không.
Saif Bechan

Nếu bạn làm cả hai (thẻ w / bài báo và trong bảng riêng biệt) thì điều này mang lại cho bạn hiệu suất cho cả tìm kiếm tập trung vào bài viết và tìm kiếm tập trung vào thẻ. Sự đánh đổi là gánh nặng của việc duy trì thông tin lặp lại. Ngoài ra, bằng cách giới hạn số lượng thẻ, bạn có thể đặt mỗi thẻ vào cột riêng của nó. Chỉ cần Chọn * từ các bài báo Nơi XXXXX và đi; không cần thiết nổ.
John

6

Việc triển khai ba bảng được đề xuất của bạn sẽ hoạt động để gắn thẻ.

Tuy nhiên, cách sử dụng tràn ngăn xếp lại khác nhau. Chúng lưu trữ các thẻ vào cột varchar trong bảng bài đăng ở dạng văn bản thuần túy và sử dụng lập chỉ mục toàn văn để tìm nạp các bài đăng khớp với các thẻ. Ví dụ posts.tags = "algorithm system tagging best-practices". Tôi chắc chắn rằng Jeff đã đề cập đến điều này ở đâu đó nhưng tôi quên ở đâu.


4
Điều này có vẻ siêu không hiệu quả. Điều gì về thứ tự thẻ? Hoặc các thẻ liên quan? (chẳng hạn như "quá trình" là tương tự như "thuật toán" hoặc một cái gì đó tương tự)
Richard DUERR

3

Giải pháp được đề xuất là cách tốt nhất - nếu không phải là cách thực tế duy nhất mà tôi có thể nghĩ ra để giải quyết mối quan hệ nhiều-nhiều giữa các thẻ và bài viết. Vì vậy, phiếu bầu của tôi là 'có, nó vẫn là tốt nhất.' Tuy nhiên, tôi muốn quan tâm đến bất kỳ lựa chọn thay thế nào.


Tôi đồng ý. Các bảng Thẻ và Bản đồ thẻ này có kích thước bản ghi nhỏ và khi được lập chỉ mục đúng cách sẽ không làm giảm hiệu suất đáng kể. Giới hạn số lượng thẻ od trên mỗi mặt hàng cũng có thể là một ý tưởng hay.
PanJanek

2

Nếu cơ sở dữ liệu của bạn hỗ trợ các mảng có thể lập chỉ mục (ví dụ như PostgreSQL), tôi sẽ đề xuất một giải pháp hoàn toàn không chuẩn hóa - lưu trữ các thẻ dưới dạng một mảng chuỗi trên cùng một bảng. Nếu không, một bảng phụ ánh xạ các đối tượng với các thẻ là giải pháp tốt nhất. Nếu bạn cần lưu trữ thêm thông tin về các thẻ, bạn có thể sử dụng một bảng thẻ riêng biệt, nhưng không ích gì khi giới thiệu phép nối thứ hai cho mỗi lần tra cứu thẻ.


POstgreSQL chỉ hỗ trợ các chỉ mục trên mảng số nguyên: postgresql.org/docs/current/static/intarray.html
Mike Chamberlain

1
Nowadys nó cũng hỗ trợ văn bản: postgresql.org/docs/9.6/static/arrays.html
luckydonald

2

Tôi muốn đề xuất MySQLicious được tối ưu hóa để có hiệu suất tốt hơn. Trước đó, hạn chế của giải pháp Toxi (3 table) là

Nếu bạn có hàng triệu câu hỏi và nó có 5 thẻ trong mỗi thẻ, thì sẽ có 5 triệu mục nhập trong bảng tagmap. Vì vậy, đầu tiên chúng ta phải lọc ra 10 nghìn mục nhập bản đồ thẻ dựa trên tìm kiếm thẻ sau đó lại lọc ra các câu hỏi phù hợp trong 10 nghìn mục đó. Vì vậy, trong khi lọc ra nếu id artical là số đơn giản thì không sao, nhưng nếu nó là loại UUID (32 varchar) thì việc lọc ra cần so sánh lớn hơn mặc dù nó được lập chỉ mục.

Giải pháp của tôi:

Bất cứ khi nào thẻ mới được tạo, hãy có bộ đếm ++ (cơ sở 10) và chuyển đổi bộ đếm đó thành base64. Bây giờ mỗi tên thẻ sẽ có id base64. và chuyển id này đến giao diện người dùng cùng với tên. Bằng cách này, bạn sẽ có tối đa hai id char cho đến khi chúng tôi có 4095 thẻ được tạo trong hệ thống của mình. Bây giờ nối nhiều thẻ này vào mỗi cột thẻ của bảng câu hỏi. Thêm dấu phân cách và sắp xếp nó.

Vì vậy, bảng trông như thế này

nhập mô tả hình ảnh ở đây

Trong khi truy vấn, hãy truy vấn trên id thay vì tên thẻ thực. Vì nó được SORTED , andđiều kiện trên thẻ sẽ hiệu quả hơn ( LIKE '%|a|%|c|%|f|%).

Lưu ý rằng dấu phân cách đơn là không đủ và chúng ta cần dấu phân cách kép để phân biệt các thẻ như sqlmysqlLIKE "%sql%"cũng sẽ trả về mysqlkết quả. Nên làLIKE "%|sql|%"

Tôi biết tìm kiếm không được lập chỉ mục nhưng bạn vẫn có thể đã lập chỉ mục trên các cột khác liên quan đến bài viết như author / dateTime, nếu không sẽ dẫn đến việc quét toàn bộ bảng.

Cuối cùng với giải pháp này, không yêu cầu liên kết bên trong nơi hàng triệu bản ghi phải được so sánh với 5 triệu bản ghi với điều kiện nối.


Nhóm, Vui lòng cung cấp ý kiến ​​đóng góp của bạn về nhược điểm của giải pháp này trong phần bình luận.
Kanagavelu Sugumar

@Nick Dandoulakis Vui lòng giúp tôi bằng cách cung cấp ý kiến ​​của bạn về giải pháp trên có hoạt động không?
Kanagavelu Sugumar

@Juha Syrjälä Giải pháp trên có ổn không?
Kanagavelu Sugumar

0
CREATE TABLE Tags (
    tag VARHAR(...) NOT NULL,
    bid INT ... NOT NULL,
    PRIMARY KEY(tag, bid),
    INDEX(bid, tag)
)

Ghi chú:

  • Điều này tốt hơn TOXI ở chỗ nó không trải qua quá nhiều: nhiều bảng gây khó khăn cho việc tối ưu hóa.
  • Chắc chắn, cách tiếp cận của tôi có thể hơi cồng kềnh hơn (so với TOXI) do các thẻ dư thừa, nhưng đó là một tỷ lệ nhỏ trong toàn bộ cơ sở dữ liệu và các cải tiến hiệu suất có thể đáng kể.
  • Nó có khả năng mở rộng cao.
  • Nó không có (vì nó không cần) AUTO_INCREMENTPK thay thế . Do đó, nó tốt hơn Scuttle.
  • MySQLicious tệ vì nó không thể sử dụng chỉ mục ( LIKEvới ký tự đại diện đứng đầu ; số lần truy cập sai trên chuỗi con)
  • Đối với MySQL, hãy đảm bảo sử dụng ENGINE = InnoDB để có được các hiệu ứng 'phân cụm'.

Các thảo luận liên quan (cho MySQL):
nhiều: nhiều danh sách có thứ tự tối ưu hóa bảng ánh xạ

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.