База данных - Разработка таблицы "События" - PullRequest
26 голосов
/ 20 апреля 2010

После прочтения советов от этой замечательной Nettuts + статьи я придумал схему таблиц, которая отделяет очень изменчивые данные от других таблиц, подвергнутых интенсивному чтению и в то же время время уменьшить количество таблиц, необходимых во всей схеме базы данных, однако я не уверен, что это хорошая идея, поскольку она не соответствует правилам нормализации, и я хотел бы услышать ваш совет, вот общая идея:


У меня есть четыре типа пользователей, смоделированных в структуре Наследование таблиц классов , в основной "пользовательской" таблице я храню данные, общие для всех пользователей (id, username, password, несколько flags, ...) вместе с некоторыми TIMESTAMP полями (date_created, date_updated, date_activated, date_lastLogin, ...).

Цитирую совет № 16 из статьи Nettuts +, упомянутой выше:

Пример 2 : у вас есть «last_login» поле в вашей таблице. Он обновляет каждый время, когда пользователь заходит на сайт. Но каждое обновление таблицы вызывает кеш запросов для этой таблицы будет очищено. Вы можете поместить это поле в другая таблица для хранения обновлений вашего минимальная таблица пользователей.

Теперь все становится еще сложнее, мне нужно отслеживать некоторую статистику пользователей, например

  • сколько уникальных раз профиль пользователя был просмотрен
  • сколько уникальных раз объявлений от определенного типа пользователя было нажано
  • сколько уникальных раз сообщений от определенного типа пользователя было замечено
  • и так далее ...

В моей полностью нормализованной базе данных это добавляет до 8-10 дополнительных таблиц, это немного, но я бы хотел, чтобы все было просто, если бы я мог, поэтому я создал следующую таблицу "events" :

|------|----------------|----------------|---------------------|-----------|
| ID   | TABLE          | EVENT          | DATE                | IP        | 
|------|----------------|----------------|---------------------|-----------|
| 1    | user           | login          | 2010-04-19 00:30:00 | 127.0.0.1 |
|------|----------------|----------------|---------------------|-----------|
| 1    | user           | login          | 2010-04-19 02:30:00 | 127.0.0.1 |
|------|----------------|----------------|---------------------|-----------|
| 2    | user           | created        | 2010-04-19 00:31:00 | 127.0.0.2 |
|------|----------------|----------------|---------------------|-----------|
| 2    | user           | activated      | 2010-04-19 02:34:00 | 127.0.0.2 |
|------|----------------|----------------|---------------------|-----------|
| 2    | user           | approved       | 2010-04-19 09:30:00 | 217.0.0.1 |
|------|----------------|----------------|---------------------|-----------|
| 2    | user           | login          | 2010-04-19 12:00:00 | 127.0.0.2 |
|------|----------------|----------------|---------------------|-----------|
| 15   | user_ads       | created        | 2010-04-19 12:30:00 | 127.0.0.1 |
|------|----------------|----------------|---------------------|-----------|
| 15   | user_ads       | impressed      | 2010-04-19 12:31:00 | 127.0.0.2 |
|------|----------------|----------------|---------------------|-----------|
| 15   | user_ads       | clicked        | 2010-04-19 12:31:01 | 127.0.0.2 |
|------|----------------|----------------|---------------------|-----------|
| 15   | user_ads       | clicked        | 2010-04-19 12:31:02 | 127.0.0.2 |
|------|----------------|----------------|---------------------|-----------|
| 15   | user_ads       | clicked        | 2010-04-19 12:31:03 | 127.0.0.2 |
|------|----------------|----------------|---------------------|-----------|
| 15   | user_ads       | clicked        | 2010-04-19 12:31:04 | 127.0.0.2 |
|------|----------------|----------------|---------------------|-----------|
| 15   | user_ads       | clicked        | 2010-04-19 12:31:05 | 127.0.0.2 |
|------|----------------|----------------|---------------------|-----------|
| 2    | user           | blocked        | 2010-04-20 03:19:00 | 217.0.0.1 |
|------|----------------|----------------|---------------------|-----------|
| 2    | user           | deleted        | 2010-04-20 03:20:00 | 217.0.0.1 |
|------|----------------|----------------|---------------------|-----------|

В основном ID относится к полю первичного ключа (id) в таблице TABLE, я считаю, что все остальное должно быть довольно простым. Одна вещь, которая мне понравилась в этом дизайне, заключается в том, что я могу отслеживать все пользовательские логины, а не только последний, и таким образом генерировать некоторые интересные метрики с этими данными.

В связи с растущим характером таблицы events я также подумал о некоторых оптимизациях, таких как:

  • # 9 : Поскольку существует только конечное число таблиц и конечное (и заранее определенное) количество событий, столбцы TABLE и EVENTS могут быть установлены вместо ENUM s VARCHAR с, чтобы сэкономить место.
  • # 14 : сохранение IP s как UNSIGNED INT s с INET_ATON() вместо VARCHAR s.
  • Сохранить DATE s как TIMESTAMP s вместо DATETIME s.
  • Вместо InnoDB / MyISAM используйте двигатель ARCHIVE ( или CSV? ).
    • Поддерживаются только INSERT с и SELECT с, а данные сжимаются на лету.

В целом, каждое событие будет занимать только 14 (несжатых) байтов, что, наверное, хорошо для моего трафика.

Плюсы:

  • Возможность хранить более подробные данные (например, логины).
  • Не нужно проектировать ( и код для ) почти дюжину дополнительных таблиц (даты и статистика).
  • Сокращает несколько столбцов на таблицу и разделяет изменчивые данные.

Минусы:

  • Нереляционный (все еще не так плохо, как EAV):
    • SELECT * FROM events WHERE id = 2 AND table = 'user' ORDER BY date DESC();
  • 6 байт на событие (ID, TABLE и EVENT).

Я более склонен придерживаться этого подхода, поскольку плюсы, кажется, намного перевешивают минусы, но я все еще немного неохотно ... Я что-то упустил? Что вы думаете об этом?

Спасибо!


@ coolgeek:

Одна вещь, которую я делаю немного иначе поддерживать и введите его идентификатор в столбец object_type (в вашем случае столбец «ТАБЛИЦА»). Вы бы хотели делатьто же самое с event_type стол.

Просто чтобы прояснить, вы имеете в виду, что я должен добавить дополнительную таблицу, которая отображает, какие события разрешены в таблице, и использовать PK этой таблицы в таблице событий вместо пары TABLE / EVENT?


@ Бен:

Это все статистические данные, полученные из существующие данные, не так ли?

Дополнительные таблицы в основном связаны со статистикой, но я не имею данных, некоторые примеры:

user_ad_stats                          user_post_stats
-------------                          ---------------
user_ad_id (FK)                        user_post_id (FK)
ip                                     ip
date                                   date
type (impressed, clicked)

Если я опущу эти таблицы, у меня не будет возможности отследить, кто, что или когда, но я не знаю, как виды могут помочь здесь.

Я согласен, что это должно быть отдельным, но больше потому что это принципиально разные данные. Что кто-то и что кто-то делает два разных вещи. Я не думаю, что волатильность так важно.

Я слышал обоими способами, и я не смог найти ничего в руководстве по MySQL, в котором говорится, что любой из них прав. В любом случае, я согласен с вами в том, что они должны быть разделенными таблицами, потому что они представляют виды данных (с дополнительным преимуществом того, что они более описательны, чем обычный подход).

Я думаю, что вам не хватает леса для деревья, так сказать.

Предикат для вашей таблицы будет «Идентификатор пользователя от IP IP на момент DATE СОБЫТИЕ В СТОЛ ", который кажется разумно, но есть проблемы.

То, что я имел в виду под «не так плохо, как EAV», это то, что все записи следуют линейной структуре и их довольно просто запрашивать, иерархической структуры нет, поэтому все запросы можно выполнить с помощью простого SELECT.

Относительно вашего второго утверждения, я думаю, вы меня не так поняли; IP-адрес не обязательно связан с пользователем. Структура таблицы должна выглядеть примерно так:

IP-адрес (IP) что-то сделал (EVENT) к ПК (ID) таблица (TABLE) на дату (DATE).

Например, в последней строке моего примера выше следует прочитать, что IP 217.0.0.1 (некоторый администратор) удалил пользователя № 2 (последний известный IP-адрес 127.0.0.2) в 2010-04-20 03: 20: 00.

Вы все еще можете присоединиться, скажем, к пользовательским событиям. для пользователей, но вы не можете реализовать ограничение внешнего ключа.

Действительно, это моя главная забота. Однако я не совсем уверен, что может пойти не так с этим дизайном, который не может пойти не так с традиционным реляционным дизайном. Я могу заметить некоторые предостережения, но пока приложение, связанное с базой данных, знает, что оно делает, я думаю, проблем быть не должно.

Еще одна вещь, которая имеет значение в этом аргументе, заключается в том, что я буду хранить гораздо больше событий, и каждое событие будет более чем удвоено по сравнению с оригинальным дизайном, поэтому имеет смысл использовать здесь механизм хранения ARCHIVE, дело в том, что он не поддерживает FK с (ни UPDATE с, ни DELETE с).

Ответы [ 3 ]

4 голосов
/ 20 апреля 2010

Я очень рекомендую этот подход. Поскольку вы, вероятно, используете одну и ту же базу данных для OLTP и OLAP, вы можете получить значительные преимущества в производительности, добавив несколько звездочек и снежинок.

У меня есть приложение для социальных сетей, которое в настоящее время находится на 65 столах. Я поддерживаю одну таблицу для отслеживания представлений объекта (блог / сообщение, форум / тема, галерея / альбом / изображение и т. Д.), Другую для рекомендации объекта и третью таблицу для суммирования операций вставки / обновления в дюжине других таблиц.

Одна вещь, которую я делаю немного по-другому, - это ведение таблицы entity_type и использование ее идентификатора в столбце object_type (в вашем случае, столбец 'TABLE'). Вы хотели бы сделать то же самое с таблицей event_type.

Уточнение для Alix - Да, вы ведете справочную таблицу для объектов и справочную таблицу для событий (это будут ваши таблицы измерений). Ваша таблица фактов будет иметь следующие поля:

id
object_id
event_id
event_time
ip_address
3 голосов
/ 20 апреля 2010

Это выглядит довольно разумно, поэтому я просто хотел опровергнуть некоторые из ваших предположений, чтобы убедиться, что у вас есть конкретные причины для того, что вы делаете.

В моей полностью нормализованной базе данных это добавляет до 8-10 дополнительных Таблицы

Это все статистические данные, полученные из существующих данных, не так ли? ( Обновление : хорошо, это не так, не обращайте внимания на следующее.) Почему это не просто представления или даже материализованные представления?

Однако сбор такой статистики может показаться медленной операцией:

  • правильная индексация может сделать это довольно быстро
  • это не обычная операция, поэтому скорость не имеет большого значения
  • устранение избыточных данных может сделать другие общие операции быстрыми и надежными

Я придумал схему таблицы, которая отделил бы очень изменчивые данные с других столов, подвергнутых тяжелым читает

Полагаю, вы говорите о том, как пользовательские (просто для выбора одной таблицы) события, которые были бы довольно нестабильными, отделены от пользовательских данных. Я согласен, что это должно быть отдельным, но больше, потому что это принципиально разные данные. То, что кто-то и кто-то делает, это две разные вещи.

Я не думаю, что волатильность так важна. СУБД уже должна позволять вам помещать файл журнала и файл базы данных на отдельные устройства, что выполняет одно и то же, и конфликт не должен быть проблемой при блокировке на уровне строк.

Нереляционный (все еще не так плохо, как EAV)

Я думаю, что вам не хватает леса, если можно так выразиться.

Предикатом для вашей таблицы будет «Идентификатор пользователя от IP-IP во время ДАТА СОБЫТИЯ В ТАБЛИЦУ», что кажется разумным, но есть проблемы. (Обновление: хорошо, так что вроде как.)

Вы по-прежнему можете присоединять, скажем, пользовательские события к пользователям, но вы не можете реализовать ограничение внешнего ключа. Вот почему EAV обычно проблематичен; действительно ли что-то точно EAV не имеет значения. Обычно это одна или две строки кода для реализации ограничения в вашей схеме, но в вашем приложении это могут быть десятки строк кода, и если к нескольким местам обращаются к одним и тем же данным, они могут легко умножиться на тысячи строки кода. Поэтому, как правило, если вы можете предотвратить некорректные данные с помощью ограничения внешнего ключа, вы гарантировано, что ни одно приложение не сделает этого.

Вы можете подумать, что события не так важны, но, например, показы рекламы - это деньги. Я бы определенно хотел выявлять ошибки, связанные с показом объявлений, на самых ранних этапах разработки.

Дополнительный комментарий

Я могу заметить некоторые предостережения, но пока приложение возиться с базой данных знает, что он делает, я думаю, там не должно быть никаких проблем.

И с некоторыми оговорками вы можете сделать очень успешную систему. При правильной системе ограничений вы можете сказать: «Если какое-либо приложение, связанное с базой данных, не знает, что оно делает, СУБД отметит ошибку». Это может потребовать больше времени и денег, чем у вас, поэтому что-то более простое, что вы можете иметь, вероятно, лучше, чем что-то более совершенное, что вы не можете. C'est la vie.

0 голосов
/ 20 апреля 2010

Я не могу добавить комментарий к ответу Бена, поэтому две вещи ...

Во-первых, было бы однозначно использовать представления в автономной базе данных OLAP / DSS; совсем другое - использовать их в своей базе данных транзакций. Высокопроизводительные пользователи MySQL рекомендуют не использовать представления там, где важна производительность

Целостность данных WRT, я согласен, и это еще одно преимущество использования звезды или снежинки с 'событиями' в качестве центральной таблицы фактов (а также использования нескольких таблиц событий, как я) Но вы не можете разработать схему ссылочной целостности вокруг IP-адресов

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...