Есть ли реальная разница в производительности между первичными ключами INT и VARCHAR?

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

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

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

14 ответов


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

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

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

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


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

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


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

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

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

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


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

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

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

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


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

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


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

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

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

  • процессор Intel® Core™ i7-7500U @ 2,70 ГГц × 4
  • 15.6 GiB RAM, из которых я обеспечил около 8 ГБ было бесплатно во время теста.
  • 148.6 ГБ SSD-накопитель, с большим количеством свободного пространства.
  • в Ubuntu 16.04 64-бит
  • MySQL Ver 14.14 дистрибутив 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)]) был заменен на just rand(0, 9) и varchar таблицы, I используются полные имена штатов США, без вырезания или расширения их до 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 таблицы.

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

  • jan_int: 21.30 sec
  • jan_int_index: 18.79 sec
  • jan_char: 21.70 sec
  • jan_char_index: 18.85 sec
  • jan_varchar: 21.76 sec
  • jan_varchar_index: 18.86 sec

что касается размеров таблицы и индекса, вот результат 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 | 
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

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


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

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

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

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

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


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

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


распространенные случаи, когда суррогат 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-байтовой строки. Быстрее, меньше, меньше соединений, читабельнее.


я столкнулся с той же дилеммой. Я сделал DW (схема созвездия) с 3 таблицами фактов, дорожно-транспортными происшествиями, транспортными средствами в авариях и жертвами в авариях. Данные включают все несчастные случаи, зарегистрированные в Великобритании с 1979 по 2012 год, и 60 таблиц измерений. Всего около 20 миллионов записей.

таблицы фактов отношения:

+----------+          +---------+
| Accident |>--------<| Vehicle |
+-----v----+ 1      * +----v----+
     1|                    |1
      |    +----------+    |
      +---<| Casualty |>---+
         * +----------+ *

RDMS: MySQL 5.6

изначально индекс аварии является varchar (цифры и буквы), с 15 цифр. Я пытался не суррогат ключи, после аварии индексы никогда не изменятся. В компьютере i7 (8 ядер) DW стал слишком медленным для запроса после 12 миллионов записей нагрузки в зависимости от размеров. После большой повторной работы и добавления суррогатных ключей bigint я получил среднее повышение производительности на 20%. Еще до низкой производительности, но действительная попытка. Im работает в настройке MySQL и кластеризации.


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


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

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

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

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

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


Как обычно, нет общих ответов. 'Это зависит!- и я не шучу. Мое понимание исходного вопроса было для ключей на небольших таблицах, таких как Country (integer id или char/varchar code), являющихся внешним ключом к потенциально огромной таблице, такой как таблица адресов/контактов.

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

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

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

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


позвольте мне сказать, что да, определенно есть разница, учитывая объем производительности (из определения коробки):

1 - Использование surrogate int быстрее в приложении, потому что вам не нужно использовать ToUpper(), ToLower(), ToUpperInvarient () или ToLowerInvarient() в коде или в запросе, и эти 4 функции имеют разные критерии производительности. См. раздел Правила производительности Microsoft. (производительность приложения)

2 - через surrogate int гарантирует, что ключ со временем не изменится. Даже коды стран могут меняться, см. Википедию, как коды ISO менялись с течением времени. Это займет много времени, чтобы изменить первичный ключ для поддеревьев. (выполнение обслуживания данных)

3 - кажется, есть проблемы с решениями ORM, такими как NHibernate, когда PK/FK не является int. (производительность разработчика)