1

В моем приложении Rails у меня есть множество таблиц базы данных, содержащих пользовательские данные. Некоторые из этих таблиц в некоторых случаях содержат много строк (до 500 000 строк на пользователя) и часто запрашиваются. Всякий раз, когда я запрашиваю любую таблицу для чего-либо, user_id текущего пользователя находится где-то в запросе - либо напрямую, если таблица имеет прямую связь с пользователем, либо через соединение, если они связаны через некоторые другие таблицы.Чтобы нормализовать или не нормализовать user_ids

Должен ли я денормализовать user_id и включать его в каждую таблицу для повышения производительности?


Вот один пример:

  • Адрес относится к пользователю, и имеет user_id
  • Конверт принадлежит пользователю, и имеет user_id
  • AddressesEnvelopes присоединяется адрес и конверта, так он имеет envelope_id и address_id - он не имеет user_id, но может попасть в него через конверт или адрес (который должен принадлежать одному пользователю).

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


Вот другой сценарий:

  • Письмо принадлежит пользователю, и имеет user_id
  • этом получатель принадлежит к письму, и имеет letter_id
  • RecepientOption принадлежит этом получатель, и имеет recepient_id

Имеет смысл дублировать user_id как у получателя, так и получателя Вариант, хотя я всегда мог добраться до него, пройдя через ассоциации, через Письмо?


Некоторые примечания:

  • Там никогда никаких предметов, которые распределяются между пользователями. Целая иерархия связанных объектов всегда принадлежит к тому же пользователю.
  • Пользовательский пользователь объектов никогда не изменяется.
  • Производительность базы данных важна, потому что это приложение, интенсивно использующее данные. Существует много запросов и много таблиц.

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

ответ

2

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

user(
    #user_id 
) 
address(
    #user_id 
, #addres_num 
) 
envelope(
    #user_id 
, #envelope_num 
) 
address_envelope(
    #user_id 
, #addres_num 
, #envelope_num 
) 

(символ # указывает на столбец первичного ключа)

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

Еще одна вещь, которая имела бы смысл с этим типом дизайна используется кластеризованные индексы (в MySQL первичный ключ таблиц InnoDB построен из clu индекс). Если вы убедитесь, что user_id всегда является первым столбцом в вашем индексе, он гарантирует, что для каждой таблицы все данные для одного пользователя хранятся на диске вместе друг с другом. Это замечательно, когда вы всегда запрашиваете user_id, но это может повредить работу, если вы запрашиваете другой объект (в этом случае дублирование, как вы, сугест, может быть лучшим решением)

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

+0

Спасибо, Роланд. Это именно то, о чем я думал.Возможно, денормализация была неправильным словом для использования, поскольку я фактически не денормализую данные из таблицы пользователя в другие таблицы, просто включив user_id в качестве ключа в таблице, где можно получить через user_id через другой ключ (например, в пример address_envelopes, где вы можете получить user_id через адрес или конверт). Кластеризованное индексирование и разбиение данных между таблицами/машинами пользователем звучит как отличная идея! –

1

Пока вы

а) получить измеримое улучшение производительности

и

б) знать, какие части базы данных являются реальными нормированные данные и которые являются избыточными улучшения

там нет причин не делать этого!

+0

Прохладный! Приятно слышать, что в этом нет ничего ужасного. Благодарю. –

1

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

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

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

+0

I второй это. 500 000 - это не так много. У вас есть идея, насколько быстрыми должны быть, и по какой сумме вы хотите повысить производительность? –

+0

Примечание. Это 500 000 записей на пользователя, а не 500 000 записей. Количество пользователей в целом может увеличиться до 100 000 без проблем масштабирования, хотя одновременные пользователи, вероятно, будут намного меньше (менее 1% от общего числа). Таким образом, с 100K активных пользователей и 500K записей на пользователя, это 50 000 000 000 записей. Вот почему я думаю, что разбиение на user_id может быть полезно в конечном итоге. Пока еще нет проблем с производительностью. Мне просто интересно, было ли хорошее движение _hypothetically_ разбивать каждую таблицу с user_id и использовать ее как первый элемент в каждом составном индексе. –

Смежные вопросы