Больше столбцов или больше значений в одном столбце — база данных SQL?

#sql #sql-server #performance #query-optimization #multiple-columns

#sql #sql-server #Производительность #оптимизация запросов #несколько столбцов

Вопрос:

У нас есть база данных со статьями, которую, IMO, можно улучшить с точки зрения размера столбца (у нас более 170 столбцов) Существует множество столбцов, если бы мы хранили только некоторые bools (1 или 0). Вместо того, чтобы иметь что-то вроде СТОЛБЦА X, Z, Y, N по отдельности, было бы более оптимальным объединить все в один столбец, называемый «XZYN», со значениями, где каждое число представляет состояние XZYN.

Пример: 1000 будет означать X = true, ZYN = false . Конечно, это состояние будет интерпретироваться из нашего кода.

Это хорошая идея?

Комментарии:

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

Ответ №1:

С точки зрения хранения, хранение данных в одном столбце, подобном этому, будет «стоить» намного дороже. bit Столбец (я предполагаю, что вы имеете в виду a bit , когда говорите «bool») очень мал по размеру, где для хранения значения, подобного 1000 которому вы, вероятно, захотите an int . An int имеет размер 4 байта, где as a bit (что неудивительно) имеет размер всего 1 бит, а столбцы muliple сгруппированы в наборы по 8.

Компонент SQL Server Database Engine оптимизирует хранение битовых столбцов. Если в таблице 8 или менее битовых столбцов, столбцы хранятся как 1 байт. Если имеется от 9 до 16 битных столбцов, столбцы хранятся как 2 байта и так далее.

Это означает, что если у вас есть 100 bit столбцов, для хранения их в виде объединенной строки вам потребуется 10 int столбцов или 6 bigint столбцов, занимающих 40 или 48 байт соответственно. Для 100 bit столбцов вы бы использовали только 13 байт (100/8 = 12,5 = 13 групп по 1 байту).

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

Однако альтернативным решением (хотя я также не рекомендую) является использование побитовой логики. Здесь вы присваиваете каждому битовому значению разное кратное, а затем объединяете их, а затем используете побитовый оператор для извлечения значения для «столбца». Допустим, например, у вас есть 8 столбцов, A-H. Вы бы присвоили каждому из них цифру в 8-битном двоичном значении:

 a = 1 = 2^0
b = 2 = 2^1
c = 4 = 2^2
d = 8 = 2^3
e = 16 = 2^4
f = 32 = 2^5
g = 64 = 2^6
h = 128 = 2^7
 

Итак, если строка хочет иметь истинное значение для a, c, f и g, сохраненное значение будет 1 4 32 64 = 101. Затем вы можете проверить, является ли значение true для этого значения, используя оператор bitwise ( amp; ):

 SELECT CASE V.I amp; 1 WHEN 0 THEN 0 ELSE 1 END AS A,
       CASE V.I amp; 2 WHEN 0 THEN 0 ELSE 1 END AS B,
       CASE V.I amp; 4 WHEN 0 THEN 0 ELSE 1 END AS C,
       CASE V.I amp; 8 WHEN 0 THEN 0 ELSE 1 END AS D,
       CASE V.I amp; 16 WHEN 0 THEN 0 ELSE 1 END AS E,
       CASE V.I amp; 32 WHEN 0 THEN 0 ELSE 1 END AS F,
       CASE V.I amp; 64 WHEN 0 THEN 0 ELSE 1 END AS G,
       CASE V.I amp; 128 WHEN 0 THEN 0 ELSE 1 END AS H
FROM (VALUES(101))V(I);
 

Это, однако, опять-таки невозможно, но, по крайней мере, использует гораздо меньше памяти, чем для хранения значения like 10100110 . Однако, если вы никогда не собираетесь выполнять фильтрацию по столбцу в WHERE , то это может стоить изучения, но если есть хоть малейший шанс, что вы могли бы, то не делайте этого (хотя смесь для обоих bit и побитовых столбцов, которые не нужно фильтровать, может быть не «плохо», чтобы уменьшить количество столбцов).

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

Ответ №2:

Кто-нибудь, глядя только на БД, когда-нибудь узнает, что означают эти значения, без кода для их интерпретации?

Это также очень затруднило бы добавление или удаление любого из этих флагов, особенно если вы пытаетесь удалить что-то, что находится в «середине» объединенной строки.

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

Вы пытаетесь решить проблему с производительностью или удобочитаемостью?

Ответ №3:

Это хорошая идея? Вероятно, нет.

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

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

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

Однако вы можете сохранить их как tinyint / char(1) или даже bit . Это уменьшило бы размер до 30 байт или даже 1 байт — хотя у вас будут NULL биты, занимающие 30 бит пространства. Другими словами, вы можете получить в основном тот же эффект, переключая типы.

Или вы можете полностью удалить все эти столбцы и просто создать другую таблицу с «атрибутами». Тогда вы могли бы хранить по одной строке на объект и, когда атрибут имеет значение true, используя имя (или справочную таблицу). Например, вместо:

 entityid   flag1   flag2   flag3
   1         1       0       1
 

У вас будет другая таблица с:

 entityid     flag
   1        'flag1'  -- or a reference to "flag1"
   1        'flag2'
 

Этот подход имеет несколько преимуществ:

  • Легко добавлять новые флаги.
  • Вы можете добавить дополнительную информацию, например, дату, когда был установлен флаг.
  • Это может занять намного, намного меньше места, если флаги разрежены.