Có ... khác.
Một cách khác để lưu trữ dữ liệu đọc/chưa đọc chi tiết cho cấu trúc diễn đàn phân cấp (bảng> phần> chuỗi, v.v.). Nó làm như vậy mà không có a) phải điền trước thông tin đã đọc/chưa đọc và b) mà không cần lưu trữ nhiều hơn các hàng U * (M/2) trong trường hợp xấu nhất của nó, trong đó U là số người dùng và M là tổng số bài đăng trong cơ sở dữ liệu (và thường là nhiều, ít hơn nhiều so với điều này)
Tôi đã nghiên cứu chủ đề này cách đây không lâu. Tôi thấy SMF/phpBB "lừa gạt" một chút trong cách lưu trữ lịch sử đọc của người dùng.Lược đồ của họ hỗ trợ lưu trữ dấu thời gian hoặc ID thư cuối cùng được đánh dấu là đã đọc trong bảng, diễn đàn, thư mục con, chủ đề (hoặc được xem trực tiếp bởi trình duyệt), như sau:
[user_id, board, last_msg_id, last_timestamp]
[user_id, hội đồng quản trị, diễn đàn, last_msg_id, last_timestamp]
[user_id, hội đồng quản trị, diễn đàn, subforum, last_msg_id, last_timestamp]
[user_id, hội đồng quản trị, diễn đàn, subforum, chủ đề, last_msg_id , last_timestamp]
Điều này cho phép người dùng đánh dấu các bảng, diễn đàn, chủ đề cụ thể, v.v., là "đã đọc". Nó đòi hỏi, tuy nhiên, hoặc là hành động trên một phần của người dùng (hoặc bằng cách đọc, hoặc tích cực bấm vào "đánh dấu là đã đọc"), và trong trường hợp của phpBB, không cung cấp cho bạn mức độ chi tiết để nói "Tôi đã thấy điều này cụ thể nhưng không phải là thông điệp cụ thể đó. " Bạn cũng có được tình huống mà bạn đọc tin nhắn cuối cùng trong một chủ đề đầu tiên (xem hoạt động mới nhất trong một chủ đề), và bạn ngay lập tức giả định đã đọc phần còn lại của chủ đề.
Nó hoạt động cho SMF và phpBB để lưu trữ những thứ như thế này vì hiếm khi bạn chỉ xem một bài đăng (chế độ xem mặc định được thiết lập cho hơn 20 bài đăng ở trang cuối cùng của chủ đề). Tuy nhiên, đối với các diễn đàn có nhiều luồng hơn (đặc biệt là các diễn đàn mà bạn đang xem thư một lúc), điều này ít hơn lý tưởng. Người dùng hệ thống này có thể sẽ chăm sóc rất nhiều nếu họ đã đọc một tin nhắn nhưng không phải là một thư khác, và có thể xem xét nó cồng kềnh để chỉ có thể đánh dấu toàn bộ phần là đã đọc, khi thực sự họ chỉ muốn một số được đánh dấu là đã đọc.
Bạn lưu trữ tin nhắn trong các bộ như thế này: [user_id, lower_msg_id, upper_msg_id]
Nhật ký lịch sử sử dụng được duy trì như sau:
Sau khi xem trang, một chức năng trông để xem nếu user_id có bản ghi nơi current_msg_id nằm giữa lower_msg_id và upper_msg_id. Nếu có, thì trang này được đọc và không cần thực hiện hành động nào. Nếu không, thì truy vấn khác phải được phát hành, lần này xác định nếu current_msg_id thấp hơn lower_msg_id (current_msg_id == lower_msg_id-1) hoặc nhiều hơn upper_msg_id (current_msg_id == upper_msg_id +1). Đây là trường hợp chúng ta phát triển ranh giới "đọc" hoặc "nhìn thấy" của chúng tôi bằng 1. Nếu chúng ta là một trong những từ xa_msg_id hoặc uppper_msg_id, thì chúng ta sẽ tăng tuple lên 1 theo hướng đó. Nếu chúng ta không tăng phạm vi tuple, thì chúng ta chèn một tuple mới, [user_id, current_msg_id, current_msg_id].
Trường hợp góc là khi hai dãy tuple tiếp cận lẫn nhau. Trong trường hợp này, khi tìm kiếm giữa ranh giới tuple thấp hơn và ranh giới trên tuple, hợp nhất hai ranh giới bằng cách thiết lập ranh giới trên của tuple thấp hơn đến ranh giới trên của tuple trên, và xóa tuple trên.
Mã ví dụ trong PHP:
function seen_bounds($usr_id, $msg_id) {
# mysql escape
$usr_id = mres($usr_id);
$msg_id = mres($msg_id);
$seen_query = "
SELECT
msb.id,
msb.lower_msg_id,
msb.upper_msg_id
FROM
msgs_seen_bounds msb
WHERE
$msg_id BETWEEN msb.lower_msg_id AND msb.upper_msg_id AND
msb.usr_id = $usr_id
LIMIT 1;
";
# See if this post already exists within a given
# seen bound.
$seen_row = query($seen_query, ROW);
if($seen_row == 0) {
# Has not been seen, try to detect if we're "near"
# another bound (and we can grow that bound to include
# this post).
$lower_query = "
SELECT
msb.id,
msb.lower_msg_id,
msb.upper_msg_id
FROM
msgs_seen_bounds msb
WHERE
msb.upper_msg_id = ($msg_id - 1) AND
msb.usr_id = $usr_id
LIMIT 1;
";
$upper_query = "
SELECT
msb.id,
msb.lower_msg_id,
msb.upper_msg_id
FROM
msgs_seen_bounds msb
WHERE
msb.lower_msg_id = ($msg_id + 1) AND
msb.usr_id = $usr_id
LIMIT 1;
";
$lower = query($lower_query, ROW);
$upper = query($upper_query, ROW);
if($lower == 0 && $upper == 0) {
# No bounds exist for or near this. We'll insert a single-ID
# bound
$saw_query = "
INSERT INTO
msgs_seen_bounds
(usr_id, lower_msg_id, upper_msg_id)
VALUES
($usr_id, $msg_id, $msg_id)
;
";
query($saw_query, NONE);
} else {
if($lower != 0 && $upper != 0) {
# Found "near" bounds both on the upper
# and lower bounds.
$update_query = '
UPDATE msgs_seen_bounds
SET
upper_msg_id = ' . $upper['upper_msg_id'] . '
WHERE
msgs_seen_bounds.id = ' . $lower['id'] . '
;
';
$delete_query = '
DELETE FROM msgs_seen_bounds
WHERE
msgs_seen_bounds.id = ' . $upper['id'] . '
;
';
query($update_query, NONE);
query($delete_query, NONE);
} else {
if($lower != 0) {
# Only found lower bound, update accordingly.
$update_query = '
UPDATE msgs_seen_bounds
SET
upper_msg_id = ' . $msg_id . '
WHERE
msgs_seen_bounds.id = ' . $lower['id'] . '
;
';
query($update_query, NONE);
}
if($upper != 0) {
# Only found upper bound, update accordingly.
$update_query = '
UPDATE msgs_seen_bounds
SET
lower_msg_id = ' . $msg_id . '
WHERE
msgs_seen_bounds.id = ' . $upper['id'] . '
;
';
query($update_query, NONE);
}
}
}
} else {
# Do nothing, already seen.
}
}
Đang tìm kiếm các bài viết chưa đọc là tìm nơi current_msg_id không tồn tại giữa bất kỳ lower_msg_id và upper_msg_id cho một người dùng nhất định (NOT EXISTS truy vấn về SQL). Nó không phải là hiệu quả nhất của các truy vấn khi thực hiện trong một cơ sở dữ liệu quan hệ, nhưng có thể được giải quyết bằng cách lập chỉ mục tích cực. Ví dụ, sau đây là một truy vấn SQL để đếm bài viết chưa đọc cho một người dùng nhất định, nhóm của khu vực thảo luận ("item") mà bài viết đang ở:
$count_unseen_query = "
SELECT
msgs.item as id,
count(1) as the_count
FROM msgs
WHERE
msgs.usr != " . $usr_id . " AND
msgs.state != 'deleted' AND
NOT EXISTS (
SELECT 1
FROM
msgs_seen_bounds msb
WHERE
msgs.id BETWEEN msb.lower_msg_id AND msb.upper_msg_id
AND msb.usr_id = " . $usr_id . "
)
GROUP BY msgs.item
;
Người sử dụng hơn đọc trên diễn đàn này, các rộng hơn các giới hạn được đánh dấu là đã đọc bởi mỗi bộ dữ liệu và các bộ dữ liệu ít hơn phải được lưu trữ. Người dùng có thể nhận được số lượt đọc chính xác so vớichưa đọc và có thể dễ dàng được tổng hợp để xem được đọc và chưa đọc trong mỗi diễn đàn, tiểu thuyết, chủ đề, v.v.
Với một diễn đàn nhỏ về khoảng 2000 bài đăng, sau đây là thống kê sử dụng về số lượng bộ nhớ được lưu trữ , được sắp xếp theo số lần người dùng đã đăng nhập (hoạt động gần đúng của người dùng). Cột "num_bounds" là số lượng bộ cần thiết để lưu trữ lịch sử xem "num_posts_read" của người dùng.
id num_log_entries num_bounds num_posts_read num_posts
479 584 11 2161 228
118 461 6 2167 724
487 119 34 2093 199
499 97 6 2090 309
476 71 139 481 82
480 33 92 167 26
486 33 256 757 154
496 31 108 193 51
490 31 80 179 61
475 28 129 226 47
491 22 22 1207 24
502 20 100 232 65
493 14 73 141 5
489 14 12 1517 22
498 10 72 132 17
Tôi chưa thấy triển khai cụ thể này trong bất kỳ diễn đàn nào, nhưng tùy chỉnh của riêng tôi và đó là một phần nhỏ ở đó. Tôi muốn được quan tâm nếu có ai khác đã thực hiện, hoặc nhìn thấy điều này được thực hiện ở nơi khác, đặc biệt là trong một diễn đàn lớn và/hoặc hoạt động.
Kính trọng,
Kaiden
Tôi vẫn nghĩ rằng phải có một cách dễ dàng hơn để làm điều đó. Tôi nghĩ về việc sử dụng MemCache, nhưng nó dựa vào bộ nhớ, và tôi vẫn đang nghĩ về APC. Nếu tôi có thể có một số loại tập tin bộ nhớ cache để làm việc với, có lẽ sẽ giúp đỡ. – yoda