Очень часто любому разработчику приходится сталкиваться с такой ситуацией:
- Проверить существует ли определенная запись в таблице.
- Если такой записи нет, то добавить ее
- Если запись уже существует, то изменить ее
Начинающий программист решит эту задачу в три запроса, приблизительно так:
$result=mysql_query("SELECT * FROM `logs` WHERE `ip`='".$ip."'");
if(mysql_num_rows($result)==0){
mysql_query("INSERT INTO `logs` (`ip`, `data`) VALUES ('".$ip."', '".$data."')");
}else{
mysql_query("UPDATE `logs` SET `data`='".$data."' WHERE `ip`='".$ip."'");
}
Ну а не начинающий знает о конструкции
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
Ну и теперь то, ради чего все это затеялось. Вот наш запрос:
mysql_query(
"INSERT INTO `test` (`ip`, `data`) VALUES ('".$ip."', '".$data."')".
"ON DUPLICATE KEY UPDATE `data`='".$data."'"
);
Как видите, запись действительно намного короче. Тестируем, и получаем следующее:
среднее время — 12 секунд! А это ровно в 10 раз лучше чем наш первый вариант. Причем обратите внимание, график — почти ровная, горизонтальная прямая. Т.е. в отличие от предыдущих вариантов он полностью не зависит от соотношения вставок к изменениям (более стабилен).
Итог
Не забывайте правильно настраивать индексы в ваших таблицах и используйте конструкцию ON DUPLICATE KEY. Это дает существенный прирост в скорости.
PS: архив с исходниками тестов: TestSource.zip (1.5 кб)
Хм, а можно глянуть на исходные коды тестов?
На мой взгляд обычный индекс в отличие от уникального должен отрабатывать быстрее, а у вас наоборот получается.
Приложил исходники тестов. См. в конце.
А и кстати, не забыли ли вы отключить кеширование в самом мускуле, проводя эти тесты?
Нет не забыл конечно, кеш был отключен
Сегодня все таки от нечего делать, глянул код вашего теста. Вы немного не верно проводили тесты. Почему? Потому что в ваших тестах данные то добавляются то изменяются случайно, т.е. вполне возможна ситуация когда вставится одна запись, затем эта запись будет изменена много раз, и только затем будут втавленны остальные записи. На общую картину это, конечно, не повлияет, но немного искривит графики.
Поидее нужно было сначала создать таблицу, к примеру на 10000 записей, и потом уже с ней проводить эксперименты, причем, сначала реализовывать вставки, а потом апдейты. Этот вариант даст наиболее точный вариант.
Возможно вы правы =)
Но, проводя этот тест, я задавлся целью сделать тест не какой то абстрактной скорости выполнения запроса, а старался приблизить условия к реальным, т.е старался определить какой из запросов будет оптимальнее при использовании в своих приложениях.
Согласитесь, в реальности редко встретишь такие скрипты которые действовали строго по предложенному вами алгоритму. Обычно данные переодически вставляются и переодически изменяются.
Хотя, возможно, специально для вас, проведу серию тестов с предложенным вами алгоритмом.
Я СОСИСКА!!!!!!!!!!
Бывает же =)
есть вопрос не по тесту))
какую книгу или статейки посоветуете почитать по проектированию бд, про типы связей(1к1 1 к многим и т.п.) ?
Вот основные:
А вообще книг на эту тематику много, попробуйте погуглить ;)
А я немного перефразирую слова автора статьи:
—
Начинающий программист решит эту задачу в три запроса, …
Не начинающий знает о конструкции в два запроса: insert … on duplicate key update …
А профи не парятся и делают это одним запросом:
Все таки у этих запросов немного разное назначение. Ну а насчет того что профи не партся и делают в один запрос — возможно это зря, т.к. в некоторых случаях REPLACE может быть хуже.
А в статье я больше делал акцент на преимущество использования индексов. Казалось бы это уже банально и об этом все знают, но вот большинство моих друзей-программистов удивлялись когда я им рассказывал о преимуществе использования индексов.
Но вы правы, зря я не упоминул про REPLACE)
Работают они действительно по разному…
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 ?
Боюсь ошибиться (специально не заглядывал в мануал), но обновится одна из двух строк. Скорее всего та с которой mysql обнаружит первый конфликт.
Я оказался прав, обновляется только одна строка. Да и, оказывается, мануал намекает:
Да и я не говорил что я против реплейса, но для каких то задач он может быть не пригоден. Ну и не разумно это на мой взгляд, удалять запись и затем вставлять новую, когда необходимо изменить, допустим, всего одно значение.
Значит это только для меня его поведение оказалось непрогназируемым :(
Я считал что что он обязан поменять на сс=4 в обеих строках, так как в обеих возникает повтор уникального индекса.
А в такой трактовке — «…следует избегать использования…» можно лишь говорить о непредсказуемости результата. Ведь вы не можете сказать первую или вторую стоку он изменит…
Если вначале был создан индекс «aа», а потом «bb», то изменит в первой строке. А если у меня коротнуло базу и я восстановил/пересоздал индекс «aa», то он уже будет менять вторую строку! Непрогнозируемо…
Да и услови «не использовать таблицы с множеством уникальных индексов» в реальной жизни редко встречается, ведь как минимум Автоинкрементное поле ID наверняка созаём уже не задумываясь…
А вообще смысл моего комментария был: Можно тремя запросами (как начинающие), можно двумя запросами (как у вас), а можно и одним…
Нет, нет, я абсолютно с вами согласен, это и называется непредсказуемое поведение, и с первого (да и не только с первого) взгляда это поведение кажется не логичным, и конечно это огромный минус такого запроса, т.к. может привести к нестабильности в работе приложения.
Просто я не пойму почему вы считаете что реплейс это заведомо лучший аналог on duplicate key во всех ситуациях? Разным задачам — разные инструменты.
Я думаю многим было бы интересно посмотреть видеоуроки, а не только текстовые по SQL. Много практики, мало воды.