Уроки SQL. Сжимаем три запроса в один.

18 комментариев

Очень часто любому разработчику приходится сталкиваться с такой ситуацией:

  • Проверить существует ли определенная запись в таблице.
  • Если такой записи нет, то добавить ее
  • Если запись уже существует, то изменить ее

Начинающий программист решит эту задачу в три запроса, приблизительно так:

Ну а не начинающий знает о конструкции

INSERT ... ON DUPLICATE KEY UPDATE ...

Что дает такая конструкция помимо краткости записи? Она дает выйгрыш в скорости! Тесты под катом.

Каждый тест проводится со следующими условиями:

  • Для каждого теста проводится 11 подтестов, в которых постепенно изменяется процентное соотношения колличества вставок (INSERT) к колличеству изменений (UPDATE), начиная от 1% и заканчивая 99%
  • Каждый подтест выполняется в цикле 20000 раз. (Выбранно методом тыка)
  • Написанна функция-враппер, которая учитывает только время выполнения запроса. Т.е. в тестах учитывается ТОЛЬКО время выполнения запросов, без времени выполнения функций самого PHP.

Тестируем SELECT+INSERT+UPDATE без индекса

Протестируем код который я уже приводил выше. И оформим результат в виде графика:

Вертикальная шкала — время. Горизонтальная — процент вставок. Как вы догадываетесь это самый худший результат, среднее время 120 секунд на один подтест.

Тестируем SELECT+INSERT+UPDATE с обычным индексом

Первый шаг на пути к оптимизации скрипта это добавление индекса в нашу таблицу, добавим обычный индекс
ALTER TABLE `logs` ADD INDEX ( `ip` )
и опять замерим скорость:

Опа, такое простое действие, а результат — улучшение времени в основном в 6 раз! В среднем выходит около 20 секунд на подтест.

SELECT+INSERT+UPDATE с уникальным индексом

Продолжаем шагать в сторону улучшения скрипта. Так как логика подобных скриптов подразумевает то что запись с каждым индексом уникальна, то по сути и индекс должен быть уникален. Удаляем предыдущий индекс из таблицы и добавляем новый
ALTER TABLE `test` ADD UNIQUE (`ip`)
Строим график:

Спорный результат, но чего не сделаешь ради правильной оптимизации (Индекс UNIQUE нужен для конструкции ON DUPLICATE KEY). Среднее же время осталось приблизительно тем же (20 секунд).

INSERT + ON DUPLICATE KEY + UPDATE

Ну и теперь то, ради чего все это затеялось. Вот наш запрос:

Как видите, запись действительно намного короче. Тестируем, и получаем следующее:
среднее время — 12 секунд! А это ровно в 10 раз лучше чем наш первый вариант. Причем обратите внимание, график — почти ровная, горизонтальная прямая. Т.е. в отличие от предыдущих вариантов он полностью не зависит от соотношения вставок к изменениям (более стабилен).

Итог

Не забывайте правильно настраивать индексы в ваших таблицах и используйте конструкцию ON DUPLICATE KEY. Это дает существенный прирост в скорости.
PS: архив с исходниками тестов: TestSource.zip (1.5 кб)

  1. Aseg

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

      1. Aseg

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

  2. Aseg

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

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

    1. Дмитрий Амиров Автор

      Возможно вы правы =)

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

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

      Хотя, возможно, специально для вас, проведу серию тестов с предложенным вами алгоритмом.

  3. mark

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

    1. Дмитрий Амиров Автор

      Вот основные:

      • «Базы данных. Проектирование, реализация и сопровождение. Теория и практика» — Томас Коннолли, Каролин Бегг
      • «Введение в системы баз данных.» — Крис Дейт

      А вообще книг на эту тематику много, попробуйте погуглить ;)

  4. Роберт

    А я немного перефразирую слова автора статьи:

    Начинающий программист решит эту задачу в три запроса, …
    Не начинающий знает о конструкции в два запроса: insert … on duplicate key update
    А профи не парятся и делают это одним запросом:

    1. Дмитрий Амиров Автор

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

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

      Но вы правы, зря я не упоминул про REPLACE)

      1. Роберт

        Работают они действительно по разному…
        REPLACE — медленней (ведь там по сути удаление и вставка)
        а ON DUPLICATE KEY — не прогнозируемый!

        Например, есть у вас таблица с полями aa, bb, cc
        aa уникальный
        bb уникальный
        Заполняем таблицу:
        insert table (aa,bb,cc) values (1,null,0)
        insert table (aa,bb,cc) values (null,2,0)
        и после этого ваш:
        insert table (aa,bb,сс) values (1,2,3) on duplicate key update сс=4
        и угадайте, какая строка получит cc=4 ?

        1. Дмитрий Амиров Автор

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

        2. Дмитрий Амиров Автор

          Я оказался прав, обновляется только одна строка. Да и, оказывается, мануал намекает:

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

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

          1. Роберт

            Значит это только для меня его поведение оказалось непрогназируемым :(
            Я считал что что он обязан поменять на сс=4 в обеих строках, так как в обеих возникает повтор уникального индекса.
            А в такой трактовке — «…следует избегать использования…» можно лишь говорить о непредсказуемости результата. Ведь вы не можете сказать первую или вторую стоку он изменит…
            Если вначале был создан индекс «aа», а потом «bb», то изменит в первой строке. А если у меня коротнуло базу и я восстановил/пересоздал индекс «aa», то он уже будет менять вторую строку! Непрогнозируемо…
            Да и услови «не использовать таблицы с множеством уникальных индексов» в реальной жизни редко встречается, ведь как минимум Автоинкрементное поле ID наверняка созаём уже не задумываясь…

            А вообще смысл моего комментария был: Можно тремя запросами (как начинающие), можно двумя запросами (как у вас), а можно и одним…

            1. Дмитрий Амиров Автор

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

              Просто я не пойму почему вы считаете что реплейс это заведомо лучший аналог on duplicate key во всех ситуациях? Разным задачам — разные инструменты.

  5. Инокентий

    Я думаю многим было бы интересно посмотреть видеоуроки, а не только текстовые по SQL. Много практики, мало воды.

Добавить комментарий

Прочли запись? Понравилась? Не стесняйтесь, оставьте, пожалуйста, свой комментарий. Мне очень интересно, что вы думаете об этом. Кстати в комментарии вы можете задать мне любой вопрос. Я обязательно отвечу.

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