Есть ли НАСТОЯЩАЯ разница в производительности между первичными ключами INT и VARCHAR? - PullRequest
151 голосов
/ 02 декабря 2008

Есть ли ощутимая разница в производительности между использованием INT и VARCHAR в качестве первичного ключа в MySQL? Я хотел бы использовать VARCHAR в качестве первичного ключа для списков ссылок (например, штаты США, коды стран), и коллега не будет указывать INT AUTO_INCREMENT в качестве первичного ключа для всех таблиц.

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

Итак, есть ли у кого-нибудь опыт использования этого конкретного варианта использования и связанные с ним проблемы производительности?

Ответы [ 14 ]

77 голосов
/ 02 декабря 2008

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

INT AUTO_INCREMENT соответствует условию «уникальность и неизменность во времени». Отсюда и предпочтение.

70 голосов
/ 02 декабря 2008

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

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

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

Другие люди отмечают, что на практике редко бывает, чтобы естественный ключ никогда не менялся или не имел дубликатов, поэтому суррогатные ключи обычно стоят того.

34 голосов
/ 02 декабря 2008

Зависит от длины. Если varchar будет 20 символов, а int равно 4, то, если вы используете int, у вашего индекса будет ПЯТЬ раз больше узлов на страницу индексного пространства на диске ... означает, что для обхода индекса потребуется одна пятая числа физических и / или логических операций чтения.

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

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

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

31 голосов
/ 02 декабря 2008

Абсолютно нет.

Я сделал несколько ... несколько ... проверок производительности между INT, VARCHAR и CHAR.

Таблица с 10 миллионами записей с ПЕРВИЧНЫМ КЛЮЧОМ (уникальным и кластерным) имела одинаковую скорость и производительность (и стоимость поддерева) независимо от того, какой из трех я использовал.

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

25 голосов
/ 02 февраля 2018

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

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

Установка была следующей:

  • Процессор Intel® Core ™ i7-7500U @ 2,70 ГГц × 4
  • 15,6 ГБ ОЗУ, из которых, как я убедился, около 8 ГБ было свободно во время теста.
  • 148,6 ГБ SSD-накопитель с большим количеством свободного места.
  • Ubuntu 16.04, 64-битная
  • MySQL Ver 14.14 Distrib 5.7.20, для Linux (x86_64)

Таблицы:

create table jan_int (data1 varchar(255), data2 int(10), myindex tinyint(4)) ENGINE=InnoDB;
create table jan_int_index (data1 varchar(255), data2 int(10), myindex tinyint(4), INDEX (myindex)) ENGINE=InnoDB;
create table jan_char (data1 varchar(255), data2 int(10), myindex char(6)) ENGINE=InnoDB;
create table jan_char_index (data1 varchar(255), data2 int(10), myindex char(6), INDEX (myindex)) ENGINE=InnoDB;
create table jan_varchar (data1 varchar(255), data2 int(10), myindex varchar(63)) ENGINE=InnoDB;
create table jan_varchar_index (data1 varchar(255), data2 int(10), myindex varchar(63), INDEX (myindex)) ENGINE=InnoDB;

Затем я заполнил 10 миллионов строк в каждой таблице скриптом PHP, суть которого такова:

$pdo = get_pdo();

$keys = [ 'alabam', 'massac', 'newyor', 'newham', 'delawa', 'califo', 'nevada', 'texas_', 'florid', 'ohio__' ];

for ($k = 0; $k < 10; $k++) {
    for ($j = 0; $j < 1000; $j++) {
        $val = '';
        for ($i = 0; $i < 1000; $i++) {
            $val .= '("' . generate_random_string() . '", ' . rand (0, 10000) . ', "' . ($keys[rand(0, 9)]) . '"),';
        }
        $val = rtrim($val, ',');
        $pdo->query('INSERT INTO jan_char VALUES ' . $val);
    }
    echo "\n" . ($k + 1) . ' millon(s) rows inserted.';
}

Для таблиц int бит ($keys[rand(0, 9)]) был заменен просто rand(0, 9), а для таблиц varchar я использовал полные имена штатов США, не сокращая и не расширяя их до 6 символов. generate_random_string() генерирует случайную строку из 10 символов.

Тогда я побежал в MySQL:

  • SET SESSION query_cache_type=0;
  • Для jan_int стол:
    • SELECT count(*) FROM jan_int WHERE myindex = 5;
    • SELECT BENCHMARK(1000000000, (SELECT count(*) FROM jan_int WHERE myindex = 5));
  • Для других таблиц, как указано выше, с myindex = 'califo' для char таблиц и myindex = 'california' для varchar таблиц.

Время запроса BENCHMARK для каждой таблицы:

  • Jan_int: 21,30 с
  • jan_int_index: 18,79 с
  • янв_чар: 21,70 с
  • jan_char_index: 18,85 с
  • Январь: 21,76 с
  • jan_varchar_index: 18,86 с

Что касается размеров таблиц и индексов, вот вывод show table status from janperformancetest; (с несколькими столбцами, которые не показаны):

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Name              | Engine | Version | Row_format | Rows    | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | Auto_increment | Collation              |
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| jan_int           | InnoDB |      10 | Dynamic    | 9739094 |             43 |   422510592 |               0 |            0 |   4194304 |           NULL | utf8mb4_unicode_520_ci |  
| jan_int_index     | InnoDB |      10 | Dynamic    | 9740329 |             43 |   420413440 |               0 |    132857856 |   7340032 |           NULL | utf8mb4_unicode_520_ci |   
| jan_char          | InnoDB |      10 | Dynamic    | 9726613 |             51 |   500170752 |               0 |            0 |   5242880 |           NULL | utf8mb4_unicode_520_ci |  
| jan_char_index    | InnoDB |      10 | Dynamic    | 9719059 |             52 |   513802240 |               0 |    202342400 |   5242880 |           NULL | utf8mb4_unicode_520_ci |  
| jan_varchar       | InnoDB |      10 | Dynamic    | 9722049 |             53 |   521142272 |               0 |            0 |   7340032 |           NULL | utf8mb4_unicode_520_ci |   
| jan_varchar_index | InnoDB |      10 | Dynamic    | 9738381 |             49 |   486539264 |               0 |    202375168 |   7340032 |           NULL | utf8mb4_unicode_520_ci | 
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

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

9 голосов
/ 02 декабря 2008

Для коротких кодов, вероятно, нет никакой разницы. Это особенно верно, поскольку таблица, содержащая эти коды, вероятно, будет очень маленькой (максимум несколько тысяч строк) и не будет часто меняться (когда мы в последний раз добавляли новый штат США).

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

6 голосов
/ 02 декабря 2008

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

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

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

ex.
id value
1 A
2 B
3 C

Update 3 to D
id value
1 A
2 B
3 D

Update 2 to C
id value
1 A
2 C
3 D

Update 3 to B
id value
1 A
2 C
3 B

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

2 голосов
/ 30 ноября 2016

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

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

Дело в том, что ЦП имеет дело с 4-байтовыми и 8-байтовыми целыми числами, естественно, в silic . Это действительно быстро для сравнения двух целых чисел - это происходит за один или два такта.

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

Мое общее правило - каждый первичный ключ должен быть автоинкрементным INT, особенно в OO-приложениях, использующих ORM (Hibernate, Datanucleus и т. Д.), Где существует множество связей между объектами - они обычно всегда реализуются как простой FK и способность БД быстро решать эти проблемы важна для вашего приложения. Отзывчивость.

2 голосов
/ 09 декабря 2015

Обычные случаи, когда суррогат AUTO_INCREMENT болит:

Распространенным шаблоном схемы является отображение «многие ко многим» :

CREATE TABLE map (
    id ... AUTO_INCREMENT,
    foo_id ...,
    bar_id ...,
    PRIMARY KEY(id),
    UNIQUE(foo_id, bar_id),
    INDEX(bar_id) );

Производительность этого шаблона намного выше, особенно при использовании InnoDB:

CREATE TABLE map (
    # No surrogate
    foo_id ...,
    bar_id ...,
    PRIMARY KEY(foo_id, bar_id),
    INDEX      (bar_id, foo_id) );

Почему?

  • Дополнительные ключи InnoDB нуждаются в дополнительном поиске; переместив пару в ПК, этого можно избежать в одном направлении.
  • Вторичный индекс - "покрытие", поэтому он не нуждается в дополнительном поиске.
  • Эта таблица меньше из-за избавления от id и одного индекса.

Другой случай ( страна ):

country_id INT ...
-- versus
country_code CHAR(2) CHARACTER SET ascii

Слишком часто новичок нормализует country_code в 4-байтовый INT вместо использования «натуральной» 2-байтовой почти неизменной 2-байтовой строки. Быстрее, меньше, меньше СОЕДИНЕНИЙ, более читабельно.

2 голосов
/ 17 октября 2012

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

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

...