Отношение один к одному в базе данных? - PullRequest
5 голосов
/ 05 октября 2008

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

Было бы лучше объединить все вместе в один стол?

Ответы [ 10 ]

10 голосов
/ 05 октября 2008

Начните с игнорирования производительности и просто создайте наиболее логичный и простой для понимания дизайн базы данных, который вы можете. Если игроки и игровые объекты (мечи? Шахматные фигуры?) Концептуально являются отдельными вещами, то поместите их в отдельные таблицы. Если игрок может нести вещи, вы помещаете внешний ключ в таблицу «вещей», которая ссылается на таблицу «игроков». И так далее.

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

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

5 голосов
/ 05 октября 2008

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

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

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

Эдит говорит : Комментируя нормализацию: Ну, я никогда не видел, чтобы база данных нормализовалась до максимума, и никто не рекомендует это в качестве опции. В любом случае, если вы точно не знаете, будут ли впоследствии нормализованы какие-либо столбцы (т. Е. Помещены в другие таблицы), то это также проще сделать с одной таблицей, а не с таблицей «таблица-один-к-одному». «

3 голосов
/ 06 октября 2008

Стоит ли хранить

информация для входа игрока [отдельно] от других в информация об игре (инвентарь, статистика, и статус)

часто является вопросом нормализации. Вы можете быстро взглянуть на определения в википедии ( Третья нормальная форма , Денормализация ). Разделяете ли вы их на несколько таблиц, зависит от того, какие данные хранятся. Расширение вашего вопроса сделает это конкретным. Данные, которые мы хотим сохранить:

  • username: уникальное имя, позволяющее пользователю войти в систему
  • passwd: пароль: связано с именем пользователя, которое гарантирует, что пользователь уникален
  • электронная почта: адрес электронной почты для пользователя; так что игра может "тыкать" пользователя, когда он недавно не играл
  • персонаж: возможно, отдельное игровое имя для персонажа
  • статус: игрок и / или персонаж в данный момент активны
  • hitpoints: пример статистики для персонажа

Мы можем хранить это как одну таблицу или как несколько таблиц.

Пример одной таблицы

Если у игроков есть один персонаж в этом игровом мире, тогда может подойти одна таблица. Например,

CREATE TABLE players(
  id         INTEGER NOT NULL,
  username   VARCHAR2(32) NOT NULL,
  password   VARCHAR2(32) NOT NULL,
  email      VARCHAR2(160),
  character  VARCHAR2(32) NOT NULL,
  status     VARCHAR2(32),
  hitpoints  INTEGER,

  CONSTRAINT pk_players PRIMARY KEY (uid)
);

Это позволит вам найти всю необходимую информацию об игроке с помощью одного запроса на столе игроков.

Пример нескольких таблиц

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

-- track information on the player
CREATE TABLE players(
  id         INTEGER NOT NULL,
  username   VARCHAR2(32) NOT NULL,
  password   VARCHAR2(32) NOT NULL,
  email      VARCHAR2(160),

  CONSTRAINT pk_players PRIMARY KEY (id)
);

-- track information on the character
CREATE TABLE characters(
  id         INTEGER NOT NULL,
  player_id  INTEGER NOT NULL,
  character  VARCHAR2(32) NOT NULL,
  status     VARCHAR2(32),
  hitpoints  INTEGER,

  CONSTRAINT pk_characters PRIMARY KEY (id)
);
-- foreign key reference
ALTER TABLE characters
  ADD CONSTRAINT characters__players
  FOREIGN KEY (player_id) REFERENCES players (id);

Этот формат требует объединений при просмотре информации как для игрока, так и для персонажа; однако это снижает вероятность обновления записей, что может привести к несогласованности. Этот последний аргумент обычно используется при защите нескольких таблиц. Например, если у вас есть проигрыватель (LotRFan) с двумя символами (gollum, frodo) в формате одной таблицы, у вас будут дублироваться поля имени пользователя, пароля и адреса электронной почты. Если игрок хочет изменить свой адрес электронной почты / пароль, вам нужно будет изменить обе записи. Если была изменена только одна запись, таблица станет несогласованной. Однако, если LotRFan хочет изменить свой пароль в формате нескольких таблиц, обновляется одна строка в таблице.

Другие соображения

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

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

2 голосов
/ 05 октября 2008

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

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

1 голос
/ 06 октября 2008

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

Когда вы кодируете свой модуль входа в систему игрока, вы не хотите думать ни о какой информации об игре. И наоборот. Будет легче поддерживать разделение интересов, если таблицы различны.

Это сводится к тому, что в вашем информационном модуле по игре нет проблем с таблицей входа игрока.

1 голос
/ 05 октября 2008

Как видите, общее согласие по этому поводу состоит в том, что "это зависит". Оптимизация производительности / запросов легко приходит на ум - как уже упоминалось @Campbell и др.

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

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

Как правило, это означает, что сопоставление доменных объектов с таблицами базы данных - это одно из лучших мест.

Я думаю, что лучше проиллюстрировать, что я имею в виду, на примере:

Случай 1: один класс / один стол

Вы думаете с точки зрения Player класса. Player.authenticate, Player.logged_in ?, Player.status, Player.hi_score, Player.gold_credits. Пока все эти действия являются действиями для одного пользователя или представляют собой однозначные атрибуты пользователя, единственная таблица должна быть отправной точкой с точки зрения логического проектирования приложения. Один класс (игрок), один стол (игроки).

Случай 2: несколько классов / одна или несколько таблиц

Возможно, мне не нравится швейцарский армейский нож Игрок дизайн класса, но я предпочитаю думать с точки зрения Пользователь , Инвентарь , Табло и Счет . User.authenticate, User.logged_in ?, Пользователь-> account.status, Scoreboard.hi_score (Пользователь), Пользователь-> account.gold_credits.

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

Практическое воплощение идеала

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

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

Но это не всегда конец истории. Существуют практические соображения, которые могут означать, что для оптимизации физической модели данных может потребоваться отдельное действие, например, проблемы параллелизма / блокировки? размер строки становится слишком большим? издержки индексации? производительность запросов и т. д.

Будьте осторожны, думая о производительности: просто потому, что это может быть одна строка в одной таблице, вероятно, не означает, что ее запрашивают только один раз, чтобы получить всю строку. Я полагаю, что сценарий многопользовательской игры может включать в себя целый ряд вызовов для получения различной информации об игроке в разное время, и игрок всегда хочет получить «текущую стоимость», которая может быть довольно динамичной. Это может привести ко многим вызовам только для нескольких столбцов в таблице каждый раз (в этом случае дизайн с несколькими таблицами может быть быстрее, чем дизайн с одной таблицей).

1 голос
/ 05 октября 2008

Отношение 1-1 - это просто вертикальное разделение. Ничего больше. сделайте это, если по какой-то причине вы не будете хранить все данные в одной таблице (например, для разделения на несколько серверов и т. д.)

0 голосов
/ 01 декабря 2008

Я согласен с Ответ Томаса Падрона-Маккарти :

Решение проблемы тысяч одновременных пользователей - не ваша проблема. Заставить людей играть в твою игру - это проблема. Начните с самого простого дизайна - сделайте игру, работающей, играбельной и легко расширяемой. Если игра вылетает, значит, вы нормализуетесь.

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

Люди смогли вывести дизайн, используемый Blizzard для World of Warcraft. Кажется, что квесты, на которых игрок находится, сохраняются вместе с персонажем. e.g.:

CREATE TABLE Players (
   PlayerID int,
   PlayerName varchar(50),
   ...
   Quest1ID int,
   Quest2ID int,
   Quest3ID int,
   ...
   Quest10ID int,
   ...
)

Первоначально вы могли участвовать не более 10 квестов, позже он был расширен до 25, например:

CREATE TABLE Players (
   PlayerID int,
   PlayerName varchar(50),
   ...
   Quest1ID int,
   Quest2ID int,
   Quest3ID int,
   ...
   Quest25ID int,
   ...
)

В отличие от сплит-дизайна:

CREATE TABLE Players (
   PlayerID int,
   PlayerName varchar(50),
   ...
)

CREATE TABLE PlayerQuests (
   PlayerID int,
   QuestID int
)

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

0 голосов
/ 05 октября 2008

Я бы посоветовал вам хранить каждый тип записи в отдельной таблице.

Логины игрока являются одним из типов записей. Инвентарь это другое. Они не должны делиться таблицей, так как большая часть информации не передается. Сделайте ваши таблицы простыми, выделяя несвязанную информацию.

0 голосов
/ 05 октября 2008

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

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

...