РЕДАКТИРОВАТЬ:
В приведенном ниже вопросе я упростил свою проблему, чтобы ее было легко объяснить. Теперь я вижу, основываясь на первых нескольких комментариях, что я упростил это. Поэтому, когда вы читаете, помните об этом новом факте: в системе может быть почти столько же издателей, сколько пользователей, и у каждого издателя может быть свой собственный список (предпочтительно тысячи) групп интересов. Короче говоря, примите это как данность, что скорость важна, и что простые списки просто не будут сокращать это …
КОНЕЦ РЕДАКТИРОВАНИЯ.
Я разрабатываю систему публикации сообщений (статей в стиле блога) для пользователей, используя MySQL и PHP. Сообщения публикуются в «группах по интересам», а пользователи подписываются на чтение групп по интересам. Когда пользователь запрашивает свою новостную ленту, я должен быть в состоянии собрать и вернуть список статей как можно быстрее.
В интересах скорости я использую побитовые операторы для выбора сообщений из базы данных. Каждая группа по интересам соответствует биту в целом числе. Каждый пост имеет «маску публикации», которая представляет собой целое число, в котором хранятся группы, в которых оно опубликовано. Каждый пользователь в качестве «маски чтения», которая представляет собой целое число, в котором хранятся группы, которые интересуют пользователя.
Например, группы интересов могут быть следующими:
В этом случае маской публикации может быть, скажем, «3» («Рыбалка и прогулка по Бушу»). Пользователь с маской чтения «5» («Рыбалка и дайвинг в небе») будет иметь доступ к статье, а пользователь с маской чтения «4» — нет. Выбор сообщений происходит в рамках SQL-запроса. Запрос просто использует предложение WHERE, которое возвращает логический результат побитового И между маской чтения пользователя и маской публикации каждого поста.
Итак … это работает очень хорошо, за исключением очевидной проблемы: я ограничен 64 группами интересов. Что касается жизни, я не могу придумать элегантного способа обойти это.
Я мог бы добавить вторую пару масок и основать предложение WHERE на ((PubMask1 AND ReadMask1) || (PubMask2 AND ReadMask2))
однако этот «линейный» подход дает мне только 128 групп. Что если я хочу, скажем, 3000?
Я посмотрел на библиотеку PHP GMP, но это не помогает — мне не нужно извлекать все из базы данных для фильтрации в PHP — и я не могу найти GMP-эквивалент в качестве плагина MySQL. (Кроме того, я не уверен, какова будет скорость для библиотек с множественной точностью).
Есть ли другие возможности, которые я упускаю? Например, есть ли способ хранить длинную и длинную строку нулей и единиц и выполнять над ними двоичную арифметику?
Одним из возможных решений было бы использовать пары масок, как указано выше (PubMask1, PubMask2, ReadMask1, ReadMask2), и позволить каждой записи записи иметь несколько записей публикации (и каждый пользователь имеет несколько записей маски чтения). В этом случае у меня может быть до 64 х 64 групп интересов, но я действительно не хочу вводить отношения один ко многим в этот высокопроизводительный сценарий, если смогу помочь.
То, что вы предлагаете здесь, это спускаться по глубокой, глубокой кроличьей норе и фактически не приведет к каким-либо улучшениям производительности. Фактически, это, вероятно, будет иметь противоположный эффект, делая вашу схему не просто неудобной в использовании, но ограниченной из-за проблем с производительностью из-за вашего нестандартного подхода к пометке. Чем больше вы идете против структуры в СУБД, такой как MySQL, тем больше вы наказываете проблемами с производительностью.
Индивидуальный подход к этому состоит в том, чтобы иметь простой таблица ассоциации который связывает посты с группами:
CREATE TABLE post_group_links(
id INT AUTO_INCREMENT PRIMARY KEY,
post_id INT NOT NULL,
group_id INT NOT NULL,
UNIQUE KEY `index_pgl_post_group` (`post_id`,`group_id`)
);
Тот UNIQUE
Ограничение индекса означает, что вы можете иметь одну и только одну связь между постом и группой. База данных, такая как MySQL, позволяет легко и быстро получить все статьи для группы:
SELECT posts.* FROM posts
LEFT JOIN post_group_links ON posts.id=post_id
WHERE post_group_links.group_id=?
Это должно работать в миллисекундах даже для больших баз данных, потому что об этом позаботится индекс. Если вы хотите упорядочить эти записи, вам может потребоваться добавить какие-либо данные для упорядочения в таблицу соединений, но добавить и включить их в индекс довольно просто.
Если вы хотите найти сообщения в более чем одной группе, используя эксклюзивные AND
это также возможно, хотя и медленнее, с чем-то вроде следующего:
SELECT posts.* FROM posts
LEFT JOIN post_group_links ON posts.id=post_id
WHERE post_group_links.group_id IN (?,?,?)
GROUP BY posts.id
HAVING COUNT(post_group_links.id)=3
Есть много способов написать это, включая использование подзапроса, если это облегчает выполнение.
Это легко масштабируется до миллионов постов в тысячах групп. Современный сервер MySQL с SSD-поддержкой даже не нарушает потоки выполняемых запросов тысячи раз в минуту и может быть настроен для более быстрой работы с использованием более продвинутых методов, таких как разбиение на разделы или горизонтальное разбиение.
Предлагаемое вами решение, включающее несколько 64-битных столбцов, является прямым нарушением важного Правило ноль, единица или бесконечность. Столбцы как PubMask1
, PubMask2
почти всегда верный признак схемы с фундаментальными проблемами проектирования.
Других решений пока нет …