Тарифы Услуги Сим-карты

Оптимизация запросов MySQL. Оптимизация запросов к базе данных mySQL

  • Разместил Николай Коротков
  • Дата: 8 декабря 2012 в 14:04

Для чего все это нужно? На что влияет? Как воплотить в реальность? На все эти вопросы я постараюсь дать четкий ответ в этом посте!

А теперь небольшая предыстория. В общем, недавно получил письмо на свой e-mail адрес, следующего содержания:

В течение последних 3 дней средний уровень нагрузки, создаваемый Вашим аккаунтом ******* , составил 119% от допустимого уровня Вашего тарифного плана. Мы рекомендуем Вам перейти на тарифы VPS. Обращаем Ваше внимание, что в случае регулярного превышения лимитов, мы оставляем за собой право заблокировать Ваш аккаунт согласно пункту Договора...

Оба на, приплыли — подумал я в тот момент! Согласитесь, не очень приятно получать такие письма. А так как с подобного рода проблемой я столкнулся впервые, представляете, в каком я был недоумении? Моему возмущению не было предела! Какой нафиг VPS? Я можно сказать только обжился на одном тарифе, а мне тут предлагают перейти на виртуальный хостинг, который в три раза дороже. Ну нет уж ребята, — думаю я, — еще рановато.

Пишу в обратку письмо моему хостеру, с просьбой пояснить мне, с какого это перепуга у меня зашкаливает нагрузка? Ведь моему блогу всего-то два с небольшим месяца от роду. Да и посещаемость не велика. В общем, пишу, что категорически против переходить на VPS, считаю, что это не целесообразно на столь раннем этапе развития ресурса и прошу указать мне на мои ошибки, что с ними делать и как в дальнейшем их контролировать!

В ответ получаю следующее:

Уважаемый абонент, мы вас не собираемся отключать именно сейчас, это банальное предупреждение, но мол надо с этим, что-то делать. Проблема превышения нагрузки не зависит напрямую от посещаемости, а в большей степени зависит от не правильной оптимизации вашего ресурса. Для отслеживания нагрузки мы вам вывели в панели управления счетчик, который обновляется каждые 10 минут:

Ну спасибо за разъяснения, — думаю про себя. Пойду изучать проблему. Набрав в интернете запрос «как снизить нагрузку на хостинг» понял, что я не один такой, а на самом деле проблема довольно актуальная. И рано или поздно коснется многих. Ознакомившись более детально с проблемой, понял, что у меня есть два выхода из данной ситуации:

  1. Обратиться за помощью к профессионалам (фрилансерам), заплатив им определенную сумму денег, что всегда успеется.
  2. Постараться устранить проблему самому.

Так вот, я выбрал второй вариант и скажу вам честно, пока что ни на грамм не пожалел. Мне удалось снизить нагрузку на хостинг в два-три раза. Вот посмотрите сами:

Разница, на лицо! Сейчас я вам покажу и расскажу, что я для этого сделал:

— оптимизировал базу данных mysql, что существенно отразилось на снижении нагрузки на хостинг и ускорении wordpress;
— избавился от порядка 8 ненужных плагинов.
— ускорил wordpress, отредактировав несколько файлов темы своего блога.

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

В общем, материал, который вы почерпнете из этих трех постов, будет ну просто обалденным. Не пропустите, !

Оптимизация базы данных

Прежде чем вы начнете производить различные действия с базой данных, обязательно делайте резервную копию . Чтобы в случае возникновения проблем можно было все быстренько восстановить. База данных содержит всю историю вашего ресурса, в ней хранятся все записи, присутствующие на вашем блоге! А вообще, советую вам взять за правило сохранять базу данных каждый день! Это у вас займет буквально 1 минуту, но зато вы будете всегда спать спокойно. Сами понимаете может случится всякое.

1. Делаем резервную копию базы данных

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

В итоге вы должны оказаться вот на такой странице, phpMyAdmin:

Заходите в базу данных, кликнув по ее названию. Перед вами откроется таблица базы данных (кликните для увеличения):

Нажимаете «Экспорт» и «ОК». Сохраняете на своем ПК. Все, база данных сохранена, теперь можем приступать к ее оптимизации. Обратите внимание, если на вашем хостинге присутствует поле «Сохранить как файл» не забудьте напротив него поставить галочку! А также запомните, сколько весит в данный момент ваша база данных, а потом посмотрите сколько она будет весить после оптимизации.

У меня она весила до оптимизации 26 Mb — это УЖАС, а что сейчас? А сейчас она весит всего 2 Mb! Представляете, сколько всякого ненужного хлама она содержала в себе? Представляете, какую нагрузку она создавала на сервере? После оптимизации базы данных, мой блог стал летать, как реактивный самолет! В общем, после того как вы проделаете все ниже описанные действия, вы почувствуете существенную разницу!

2. Отключаем ревизии постов и устанавливаем минимальный срок хранения удаленных файлов в корзине

Что такое ревизия постов? Когда вы пишите пост в блог, wordpress автоматически, через определенный промежуток времени, сохраняет резервную копию каждого поста в базе данных, в общем, делает авто сохранение. А теперь представьте когда вы напишите 50 постов на блоге? Сколько копий постов у вас будет сохранено? Это ЖЕСТЬ! Пока вы пишите пост, у вас уже как минимум проходит 10 авто сохранений!

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

Вот основная часть последствий, но далеко не всех: снижение , частые отказы, ухудшение , понижение позиций в выдаче поисковиков... А дальше, автор в подает в отчаяние от не оправданных ожиданий. Желание вести блог со временем пропадает и все! КРАХ!

Все это я к чему говорю? За базой данных постоянно нужно следить и содержать ее в надлежащем состоянии. Поймите, база данных — это как сердце блога. При постоянной нагрузке на сердце не нужным хламом, со временем оно не выдержит и ОСТАНОВИТСЯ! Я думаю, вы меня поняли? Поэтому хватит ужастиков и переходим к оптимизации базы данных.

Итак, открываем файл wp-config.php, он находится в корне вашего блога, т.е. ваш хостинг/httpdocs или public_html (в зависимости от хостинга)/wp-config.php. И вставляем в него две строчки:

1 2 define ("WP_POST_REVISIONS" , false) ; define ("EMPTY_TRASH_DAYS" , 1 ) ;

Строка №1 отключает ревизию постов, строка №2 означает, сколько дней будут храниться удаленные файлы в вашей корзине. Как видите, я поставил «1», можно конечно поставить и «0», но если вдруг по неосторожности у вас дрогнет рука и вы нажмете на ссылку «удалить», все — КАПЕЦ!

А после просиживания за компом 5-8 часов, поверьте мне, это возможно! Так что я предпочитаю оставить циферку «1». Конечно, после удаления файла лучше сразу же почистить корзину вручную, но если даже вы забудете это сделать, спустя сутки файл из корзины автоматически удалится! Вот как это выглядит у меня:

3. Удаляем ревизии постов

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

Переходим снова в базу данных MySQL, как описано в первом пункте. Заходим во вкладку SQL, вставляем в поле скопированную строчку и нажимаем «ОК»:

База данных спросит:

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

Я делал чистку 3 дня назад, поэтому у меня она еще не обросла ревизиями. Когда я первый раз почистил базу, у меня было удалено аж 1800 с чем-то строк! Представляете, сколько копий ненужных постов в ней хранилось? Идем дальше.

4. Оптимизируем записи в wp-post

Папка wp-post содержит все записи блога. Точно так же как и в предыдущем пункте, копируем строку:

OPTIMIZE TABLE wp_posts;

И вставляем в поле SQL запроса. Нажимаем «ОК», смотрим:

Все, запрос выполнен!

5. Чистим wp-postmeta

Что именно будем чистить? Папка wp-postmeta содержит в себе:

— время последнего редактирования какого-либо из постов. Значения никакого не имеет, а нагрузку на сервер, какую никакую, а дает;
— содержание предыдущего (человека понятного урла). Если вы когда-нибудь меняли постоянную ссылку в любом посте. То при смене ее, она не удаляется, а оседает в папке wp-postmeta и нагружает вашу базу.

Делаем все тоже самое, копируем вот этот код:

Вставляем его в поле запроса SQL, и жмем «ОК». Смотрим на результат:

6. Удаляем спам-комментарии

Делается аналогично, копируем код:

Вставляем в поле SQL запроса, жмем «ОК», смотрим результат:

Как вы видите «0». После выполнения этого запроса, вы забудете про спам комментарии!

7. Удаляем пингбеки

Пингбеки — это уведомления о том, что на ваш пост или страницу кто-то ссылается. Нам это не нужно, лишняя нагрузка! Удаляем!

8. Отключаем пингбеки

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

UPDATE wp_posts p SET p. ping_status = "closed"

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

9. Устанавливаем плагин Optimize DB

Об этом плагине я уже вкратце упоминал . Ну давайте более подробно рассмотрим, как им пользоваться. Данный плагин, как вы уже догадались, способствует оптимизации базы данных! Скачайте архив с плагином себе на ПК, вот и активируйте его:

Все, ваша база данных оптимизирована дополнительно при помощи плагина:

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

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

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

И на последок порция приколов:

Ну как вам статья? Я уверен, что вы останетесь довольны после ее прочтения и проделанных рекомендаций со своим ресурсом! Жду ваших комментариев!

Понравилась статья? Поделись с друзьями!

Каждому комментатору книга в подарок!

Книга включает в себя подробное описание самых эффективных методов продвижения вашего ресурса!


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

  1. Александр 8 декабря 2012 15:18

    А я знаю почему у Тебя нагрузка так выросла. Просто я тут у Тебя прижился, и постоянно что то изучаю. А что делать если инфа здесь классная. А если серьезно, то все вышеперечисленные советы рекомендую сделать всем блоговодам в первую очередь. Я это давно сделал, поэтому сплю спокойно. И еще, плагин Optimize DB, это вообще обязательный атрибут любого Блога. Спасибо Коля, как всегда, все полезно и актуально. А вот следующий пост вообще жду с нетерпением. Так что давай, пиши

  2. 9 декабря 2012 16:19

    Я в базе данных ковыряться побаиваюсь, но после установки и чистки плагином WP-Cleanup она у меня уменьшилась с почти 50 до 7Mb. Блог действительно стал грузиться намного быстрее.

  3. 9 декабря 2012 20:39

    Строго говоря, спрашивает при операциях с базой данных не сама БД (СУБД вообще все действия одинаковы, ничего не спрашивает), а клиент, phpMySql.

    Касательно пингбэков, «Из прошлого пункта мы выяснили, что пингбеки не несут никакой пользы для нашего ресурса, а только его засоряют.» — строго говоря, ничего не выяснили.

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

  4. 10 декабря 2012 08:36
  5. Юрий 16 декабря 2012 23:49

    Привет, дружище!

    Твой пост и в самом деле классный. В Интернете столько много бредни написано, что информацию приходится искать по крупицам. А здесь я зашел, и на тебе, все доходчиво и понятно. У меня как раз началась проблема с нагрузкой на сервер. Еще советую установить плагин WP Super Cache. Только его нужно грамотно настроить. Классный плагин! Может у тебя в остальных постах о нем что-то и сказано, но я еще не читал. Спешу перейти ко второй части оптимизации. Удачи тебе и твоему блогу

  6. 25 декабря 2012 11:40
  7. 28 января 2013 11:24

    Добрый день! Очень интересно, а как быть мне с блогом на Blogger? Все плагины для WP не годятся для Блогспот, нужно искать методы оптимизации самостоятельно в инете.

    С уважением, Вадим.

  8. Антон 2 апреля 2013 20:34

    Спасибо, пост действительно добротный. У меня, кстати, после проделывания пункта №3 — «Удалено 4145 строк. (Запрос занял 7.0269 сек.)»

  9. 14 июля 2013 19:04

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

  10. 14 июля 2013 19:06

    Вдогонку: а еще очень похоже на ваш текст вот тут dayafternight.ru/wordpress/baza-dannih-mysql-optimizacia

  11. 12 сентября 2013 12:57

    Спасибо Николай, нужная вещь.

    Все доступно и понятно написано.

    А статья про коды уже вышла?

  12. 12 сентября 2013 13:05

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

    Подскажите откуда она могла появиться?

    в последнее время только код яндекс-метрики вставляла.

    Наталья Гегер

    Не обращайте на это внимание... На большинстве современных серверов она есть! Связано это с выходом MySQL версии 5.0 и выше...

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

  13. 27 октября 2013 01:06

    ох, почистил базу по вашему методу + от себя ручками, результат на лицо. Раньше база весила 20мб, сейчас 5мб

  14. 29 октября 2013 23:34

    Спасибо огромное за статью. Сегодня тоже получил втык от хостера. В результате действий, база из 25Мб стала 5,2. Есть 2 вопроса, эти все манипуляции надо делать периодически? И второй вопрос, установил плагин, нажимаю оптимизировать, в результате напротив каждой строки пишется,

    note: Table does not support optimize, doing recreate + analyze instead

    Не похоже, что всё хорошо?!

    Пожалуйста! Да, я делаю все эти манипуляции, примерно один раз в месяц. А вот насчет плагина пока не могу ничего сказать, видимо вы что-то сделали не правильно. Попробуйте поискать информацию в интернет по этому поводу. Но есть и приятные события. Вы оставили на моем блоге 2100-й комментарий и за это вам полагается приз в размере 100 рублей:

    Присылайте номер своего wmr-кошелька и я перечислю вам деньги.

  15. 30 октября 2013 13:27

    Спасибо, приз получен. Как я оказался на Вашем сайте?! Вчера очередной раз сайт перестал работать, а на экране писалось «Ошибка соединения с базой данных». Написал хостеру, там подтвердили что большая нагрузка на MySQL и что-то с этим делайте, а пока перевели на тариф выше. Сразу же начал искать, что же делать и нашёл Вашу статью, которая уменьшила базу в 5 раз. Плагин который сначала не хотел работать, всё таки заработал, но основная проблема, убрать лишние запросы, так и не была решена. У меня уже стоит плагин WP Super Cache, но он кеширует страницы, а не запросы к БД. И вот я до четырёх часов утра искал плагин, который мне сможет помочь с запросами и нашёл. WP File Cache кеширует запросы, количество запросов и МБ памяти, уменьшается в разы. На страницах где до этого было 40 запросов и 35МБ, теперь запросов 9 и 12МБ. Единственное, скорость загрузки вроде чуток увеличилось, но незначительно, учитывая что скорость загрузки страниц у меня, в среднем 0,15-0,5 секунды. Может кому то данная информация будет интересна.

  16. 7 декабря 2013 15:41

    выше указанные действия могут повлиять на работу плагина nrelate-flyout ?

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

В таких случаях, использование метода LIMIT 1 может существенно увеличить производительность:

// существуют ли в базе данные людей из Калифорнии? // НЕТ, таких нет!: $r = mysql_query("SELECT * FROM user WHERE state = "California""); if (mysql_num_rows($r) > 0) { // ... прочий код } // Положительный ответ $r = mysql_query("SELECT 1 FROM user WHERE state = "California" LIMIT 1"); if (mysql_num_rows($r) > 0) { // ... прочий код }

2. Оптимизация работы с базой с помощью обработки кэша запросов

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

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

// если кэш запросов НЕ поддерживается $r = mysql_query("SELECT username FROM user WHERE signup_date >= CURDATE()"); // кэш поддерживается! $today_date = date("Y-m-d"); $r = mysql_query("SELECT username FROM user WHERE signup_date >= "$today_date"");

3. Индексация полей поиска

Индексы предназначены не только для присвоения первичному или уникальному ключам. Если в таблице есть столбцы, по которым вы производите поиск, их практически в обязательном порядке следует индексировать.

Как вы понимаете, это правило также распространяется на часть строки поиска: такую как «last_name LIKE ‘% ‘». Когда поиск производится по началу строки, MySQL может использовать для этого столбца индексацию.

Вы также должны понимать, какие виды запросов не могут использовать обычные индексы. Например, при поиске слова (например, «WHERE post_content LIKE ‘%tomato%"»), применение обычного индекса вам ничего не даст. В таком случае лучше будет использовать поиск MySQL на полное соответствие или создать свой собственный индекс.

4. Индексирование и использование столбцов одинакового типа при объединении

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

Кроме того, столбцы, которые объединяются, должны быть одинакового типа. Например, если вы объединяете столбец типа DECIMAL из одной таблицы и столбец типа INT из другой, MySQL не сможет использовать по крайней мере один из индексов.

Даже кодировка символов должна быть того же типа для соответствующих строк объединяемых столбцов.

// ищем компании, находящиеся в моем штате $r = mysql_query("SELECT company_name FROM users LEFT JOIN companies ON (users.state = companies.state) WHERE users.id = $user_id"); // оба столбца штатов должны быть проиндексированы // и они оба должны быть одинакового типа и иметь ту же кодировку символов для соответствующих строк // или MySQL придется сканировать всю таблицу полностью

5. По возможности не используйте запросы типа SELECT *

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

// нежелательный запрос $r = mysql_query("SELECT * FROM user WHERE user_id = 1"); $d = mysql_fetch_assoc($r); echo "Welcome {$d["username"]}"; // лучше использовать следующий код: $r = mysql_query("SELECT username FROM user WHERE user_id = 1"); $d = mysql_fetch_assoc($r); echo "Welcome {$d["username"]}";

6. Пожалуйста, не используйте метод сортировки ORDER BY RAND()

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

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

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

// какой код НЕ следует использовать: $r = mysql_query("SELECT username FROM user ORDER BY RAND() LIMIT 1"); // правильнее будет использовать следующий код: $r = mysql_query("SELECT count(*) FROM user"); $d = mysql_fetch_row($r); $rand = mt_rand(0,$d - 1); $r = mysql_query("SELECT username FROM user LIMIT $rand, 1");

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

7. Используйте столбцы типа ENUM вместо VARCHAR

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

Если у вас есть некоторое поле, которое содержит несколько разных значений одного вида, то вместо столбцов типа VARCHAR лучше использовать ENUM. Например, это может быть столбец «Статус », который содержит только такие значения, как «активно », «неактивно », «ожидание », «срок действия истек » и т.д.

Существует даже возможность задать сценарий, при котором MySQL будет «предлагать» изменить структуру таблицы. Когда у вас есть поле типа VARCHAR, система может автоматически рекомендовать изменить формат столбца на ENUM. Это можно сделать с помощью вызова функции PROCEDURE ANALYSE() .

Используйте для хранения IP-адресов поля типа UNSIGNED INT

Многие разработчики создают для этих целей поля типа VARCHAR (15) , в то время как IP-адреса можно было бы хранить в базе в виде десятичных чисел. Поля типа INT предоставляют возможность хранить до 4 байта информации, и при этом для них можно задать фиксированный размер поля.

Вы должны удостовериться, что ваши колонки имеют формат UNSIGNED INT , поскольку IP-адрес задается 32-мя битами.

В запросах можно использовать параметр INET_ATON () для преобразования IP-адресов в десятичные числа, и INET_NTOA () — наоборот. PHP имеет и другие аналогичные функции long2ip () и ip2long () .

8. Вертикальное секционирование (разделение)

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

Пример 1: Допустим, у вас есть таблица пользователей, в которой в числе прочего содержатся их домашние адреса. Данная информация используется очень редко. Вы можете разделить вашу таблицу и хранить данные по адресам в другой таблице.

Таким образом, ваша основная таблица пользователей заметно уменьшится в размерах. А как вы знаете, меньшие таблицы, обрабатываются быстрее.

Пример 2: У вас в таблице есть поле «last_login » (последний логин). Оно обновляется каждый раз, когда пользователь входит в систему под своим именем пользователя. Но каждое изменение таблицы записывается в кэш запросов к этой таблице, который хранится на диске. Вы можете переместить это поле в другую таблицу, чтобы уменьшить количество обращений к вашей основной таблице пользователей.

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

9. Меньшие столбцы – быстрее

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

В MySQL Docs прописан ряд требований к хранению разных типов данных. Если ожидается, что таблица не будет содержать слишком большое количество записей, то нет причин хранить первичный ключ в полях типа INT, MEDIUMINT, SMALLINT , а в отдельных случаях даже TINYINT . Если в формате даты вам не нужны составляющие времени (часы: минуты), то используйте поля типа DATE вместо DATETIME.

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

9 октября 2008 в 23:37

Оптимизация MySQL запросов

  • MySQL

В повседневной работе приходится сталкиваться с довольно однотипными ошибками при написании запросов.

В этой статье хотелось бы привести примеры того, как НЕ надо писать запросы.

  • Выборка всех полей
    SELECT * FROM table

    При написании запросов не используйте выборку всех полей - "*". Перечислите только те поля, которые вам действительно нужны. Это сократит количество выбираемых и пересылаемых данных. Кроме этого, не забывайте про покрывающие индексы. Даже если вам на самом деле необходимы все поля в таблице, лучше их перечислить. Во-первых, это повышает читабельность кода. При использовании звездочки невозможно узнать какие поля есть в таблице без заглядывания в нее. Во-вторых, со временем количество столбцов в вашей таблице может изменяться, и если сегодня это пять INT столбцов, то через месяц могут добавиться TEXT и BLOB поля, которые будут замедлять выборку.

  • Запросы в цикле.
    Нужно четко представлять себе, что SQL - язык, оперирующий множествами. Порой программистам, привыкшим думать терминами процедурных языков, трудно перестроить мышление на язык множеств. Это можно сделать довольно просто, взяв на вооружение простое правило - «никогда не выполнять запросы в цикле». Примеры того, как это можно сделать:

    1. Выборки
    $news_ids = get_list("SELECT news_id FROM today_news ");
    while($news_id = get_next($news_ids))
    $news = get_row("SELECT title, body FROM news WHERE news_id = ". $news_id);

    Правило очень простое - чем меньше запросов, тем лучше (хотя из этого, как и из любого правила, есть исключения). Не забывайте про конструкцию IN(). Приведенный код можно написать одним запросом:
    SELECT title, body FROM today_news INNER JOIN news USING(news_id)

    2. Вставки
    $log = parse_log();
    while($record = next($log))
    query("INSERT INTO logs SET value = ". $log["value"]);

    Гораздо более эффективно склеить и выполнить один запрос:
    INSERT INTO logs (value) VALUES (...), (...)

    3. Обновления
    Иногда бывает нужно обновить несколько строк в одной таблице. Если обновляемое значение одинаковое, то все просто:
    UPDATE news SET title="test" WHERE id IN (1, 2, 3).

    Если изменяемое значение для каждой записи разное, то это можно сделать таким запросом:
    UPDATE news SET
    title = CASE
    WHEN news_id = 1 THEN "aa"
    WHEN news_id = 2 THEN "bb" END
    WHERE news_id IN (1, 2)

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

  • Выполнение операций над проиндексированными полями
    SELECT user_id FROM users WHERE blogs_count * 2 = $value

    В таком запросе индекс использоваться не будет, даже если столбец blogs_count проиндексирован. Для того, чтобы индекс использовался, над проиндексированным полем в запросе не должно выполняться преобразований. Для подобных запросов выносите функции преобразования в другую часть:
    SELECT user_id FROM users WHERE blogs_count = $value / 2;

    Аналогичный пример:
    SELECT user_id FROM users WHERE TO_DAYS(CURRENT_DATE) - TO_DAYS(registered) <= 10;

    Не будет использовать индекс по полю registered, тогда как
    SELECT user_id FROM users WHERE registered >= DATE_SUB(CURRENT_DATE, INTERVAL 10 DAY);
    будет.

  • Выборка строк только для подсчета их количества
    $result = mysql_query(«SELECT * FROM table», $link);
    $num_rows = mysql_num_rows($result);
    Если вам нужно выбрать количество строк, удовлетворяющих определенному условию, используйте запрос SELECT COUNT(*) FROM table, а не выбирайте все строки лишь для того, чтобы подсчитать их количество.
  • Выборка лишних строк
    $result = mysql_query(«SELECT * FROM table1», $link);
    while($row = mysql_fetch_assoc($result) && $i < 20) {

    }
    Если вам нужны только n строк выборки, используйте LIMIT, вместо того, чтобы отбрасывать лишние строки в приложении.
  • Использование ORDER BY RAND()
    SELECT * FROM table ORDER BY RAND() LIMIT 1;

    Если в таблице больше, чем 4-5 тысяч строк, то ORDER BY RAND() будет работать очень медленно. Гораздо более эффективно будет выполнить два запроса:

    Если в таблице auto_increment"ный первичный ключ и нет пропусков:
    $rnd = rand(1, query("SELECT MAX(id) FROM table"));
    $row = query("SELECT * FROM table WHERE id = ".$rnd);

    Либо:
    $cnt = query("SELECT COUNT(*) FROM table");
    $row = query("SELECT * FROM table LIMIT ".$cnt.", 1");
    что, однако, так же может быть медленным при очень большом количестве строк в таблице.

  • Использование большого количества JOIN"ов
    SELECT
    v.video_id
    a.name,
    g.genre
    FROM
    videos AS v
    LEFT JOIN
    link_actors_videos AS la ON la.video_id = v.video_id
    LEFT JOIN
    actors AS a ON a.actor_id = la.actor_id
    LEFT JOIN
    link_genre_video AS lg ON lg.video_id = v.video_id
    LEFT JOIN
    genres AS g ON g.genre_id = lg.genre_id

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

  • Использование LIMIT
    SELECT… FROM table LIMIT $start, $per_page

    Многие думают, что подобный запрос вернет $per_page записей (обычно 10-20) и поэтому сработает быстро. Он и сработает быстро для нескольких первых страниц. Но если количество записей велико, и нужно выполнить запрос SELECT… FROM table LIMIT 1000000, 1000020, то для выполнения такого запроса MySQL сначала выберет 1000020 записей, отбросит первый миллион и вернет 20. Это может быть совсем не быстро. Тривиальных путей решения проблемы нет. Многие просто ограничивают количество доступных страниц разумным числом. Также можно ускорить подобные запросы использованием покрывающих индексов или сторонних решений (например sphinx).

  • Неиспользование ON DUPLICATE KEY UPDATE
    $row = query("SELECT * FROM table WHERE id=1");

    If($row)
    query("UPDATE table SET column = column + 1 WHERE id=1")
    else
    query("INSERT INTO table SET column = 1, id=1");

    Подобную конструкцию можно заменить одним запросом, при условии наличия первичного или уникального ключа по полю id:
    INSERT INTO table SET column = 1, id=1 ON DUPLICATE KEY UPDATE column = column + 1

Читайте

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

Оптимизация конфигурации

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

Мы надеемся, что вы используете Linux или что-то вроде Vagrant -box (как наш Homestead Improved), а, соответственно, ваш конфигурационный файл будет находиться в /etc/mysql/my.cnf . Вполне возможно, что ваша установка на самом деле будет подгружать дополнительный файл конфигурации в этот. Так что посмотрите, если файл my.cnf содержит немного, то посмотрите в /etc/mysql/mysql.conf.d/mysqld.cnf .

Ручной тюнинг

Следующие настройки должны быть сделаны "из коробки". Согласно этим советам , добавьте в файл конфига в раздел :

Innodb_buffer_pool_size = 1G # (здесь поменяйте примерно 50%-70% от общего объема оперативы) innodb_log_file_size = 256M innodb_flush_log_at_trx_commit = 1 # можно поменять на 2 или 0 innodb_flush_method = O_DIRECT

  • innodb_buffer_pool_size . Пул буферизации (buffer pool) является эдаким "складом" для кэширования данных и индексов в памяти. Он используется, чтобы хранить часто используемые данные в памяти. И когда вы используете выделенный или виртуальный сервер, на котором зачастую именно БД является узким местом, то есть смысл отдать ей бОльшую часть оперативы. Следовательно, мы даем ей 50-70% всей RAM. В документации MySQL есть руководство по настройке этого пула .
  • innodb_log_file_size . Настройка размера лог-файла хорошо описана , но в двух словах это количество хранимых данных в логах, прежде чем его почистят. Обратите внимание, что лог в этом случае - это не записи об ошибках, а некий дельта-слепок изменений, которые ещё не были сброшены на диск в основные файлы innodb. MySQL пишет в фоновом режиме, но это все же влияет на производительность в момент записи. Большой лог-файл означает более высокую производительность из-за малого количества создаваемых новых и небольших контрольных точек, но при этом более длительное время восстановления в случае краша (больше данных должно быть переписано в БД).
  • innodb_flush_log_at_trx_commit описан и показывает, что происходит с файлом логов. Значение 1 - самое безопасное, т. к. лог сбрасывается на диск после каждой транзакции. При значениях 0 и 2 - меньше гарантируется ACID , но больше производительность. Разница не является достаточно большой, чтобы перевесить преимущества стабильности при 1.
  • innodb_flush_method . В довершение всего того, что касается сброса данных, эту настройку нужно установить в O_DIRECT - чтобы избежать двойной буферизации. Советую всегда это делать, пока система ввода-вывода остаётся очень медленной. Хотя на большинстве хостингах, типа DigitalOcean, вы будете иметь SSD-диски, поэтому система ввода-вывода будет более производительна.

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

Variable Inspector

Установка variable inspector на Ubuntu:

Wget https://repo.percona.com/apt/percona-release_0.1-4.$(lsb_release -sc)_all.deb sudo dpkg -i percona-release_0.1-4.$(lsb_release -sc)_all.deb sudo apt-get update sudo apt-get install percona-toolkit

Для других систем, следуйте этим инструкциям .

Затем запустите toolkit:

Pt-variable-advisor h=localhost,u=homestead,p=secret

Вы увидите такой результат:

# WARN delay_key_write: MyISAM index blocks are never flushed until necessary. # NOTE max_binlog_size: The max_binlog_size is smaller than the default of 1GB. # NOTE sort_buffer_size-1: The sort_buffer_size variable should generally be left at its default unless an expert determines it is necessary to change it. # NOTE innodb_data_file_path: Auto-extending InnoDB files can consume a lot of disk space that is very difficult to reclaim later. # WARN log_bin: Binary logging is disabled, so point-in-time recovery and replication are not possible.

Прим. переводчика:
На моей локальной машине, кроме этого, выдал ещё вот такой ворнинг:

# NOTE innodb_flush_method: Most production database servers that use InnoDB should set innodb_flush_method to O_DIRECT to avoid double-buffering, unless the I/O system is very low performance.

О том, что параметр innodb_flush_method нужно установить в O_DIRECT и почему говорилось выше. И если вы придерживались последовательности тюнинга как в статье, то вы не увидите это предупреждение.

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

Примечание: в новых версиях размер binlog-а по умолчанию 1G и этого ворнинга не будет.

Max_binlog_size = 1G log_bin = /var/log/mysql/mysql-bin.log server-id=master-01 binlog-format = "ROW"

  • max_binlog_size . Определяет насколько большими будут бинарные логи. В них записываются ваши транзакции и запросы и делаются контрольные точки. Если транзакция превышает максимум, то лог может превышать свой размер при сохранении на диск; в противном случае MySQL будет поддерживать его в рамках этого лимита.
  • log_bin . Эта опция включает запись бинарных логов в целом. Без неё невозможны снапшоты или репликации. Обратите внимание, что это может очень сказаться на дисковом пространстве. server-id - это необходимая опция при включении бинарного лога, поэтому логи "знают" с какого сервера они пришли (для репликации), а binlog-format - это просто способ, которым они записываются.

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

MySQL Tuner

Вспомогательные инструменты: Percona Toolkit для выявления дублирующих индексов

Ранее установленный нами Percona Toolkit также имеет инструмент для обнаружения дублирующих индексов, который может пригодиться при использовании сторонних CMS или просто проверить себя - вдруг вы случайно добавили больше индексов, чем нужно. Например, установка WordPress по умолчанию имеет дублирующие индексы в таблице wp_posts:

Pt-duplicate-key-checker h=localhost,u=homestead,p=secret # ######################################################################## # homestead.wp_posts # ######################################################################## # Key type_status_date ends with a prefix of the clustered index # Key definitions: # KEY `type_status_date` (`post_type`,`post_status`,`post_date`,`ID`), # PRIMARY KEY (`ID`), # Column types: # `post_type` varchar(20) collate utf8mb4_unicode_520_ci not null default "post" # `post_status` varchar(20) collate utf8mb4_unicode_520_ci not null default "publish" # `post_date` datetime not null default "0000-00-00 00:00:00" # `id` bigint(20) unsigned not null auto_increment # To shorten this duplicate clustered index, execute: ALTER TABLE `homestead`.`wp_posts` DROP INDEX `type_status_date`, ADD INDEX `type_status_date` (`post_type`,`post_status`,`post_date`);

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

Вспомогательные инструменты: Percona Toolkit для неиспользуемых индексов

Percona Toolkit может также обнаружить неиспользуемые индексы. Если вы логируете медленные запросы (см. раздел "узкие места" ниже), вы можете запустить утилиту и она будет проверять, используют ли эти запросы индексы в таблицах и как именно.

Pt-index-usage /var/log/mysql/mysql-slow.log

Подробную информацию об использовании этой утилиты см. .

Узкие места

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

Для начала, давайте включим логирование медленных запросов:

Slow_query_log = /var/log/mysql/mysql-slow.log long_query_time = 1 log-queries-not-using-indexes = 1

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

Как только в этом логе появятся некоторые данные, вы можете проанализировать их на предмет использования индексов с помощью вышеуказанной утилиты pt-index-usage или с помощью pt-query-digest , которая выведет примерно такие результаты:

Pt-query-digest /var/log/mysql/mysql-slow.log # 360ms user time, 20ms system time, 24.66M rss, 92.02M vsz # Current date: Thu Feb 13 22:39:29 2014 # Hostname: * # Files: mysql-slow.log # Overall: 8 total, 6 unique, 1.14 QPS, 0.00x concurrency ________________ # Time range: 2014-02-13 22:23:52 to 22:23:59 # Attribute total min max avg 95% stddev median # ============ ======= ======= ======= ======= ======= ======= ======= # Exec time 3ms 267us 406us 343us 403us 39us 348us # Lock time 827us 88us 125us 103us 119us 12us 98us # Rows sent 36 1 15 4.50 14.52 4.18 3.89 # Rows examine 87 4 30 10.88 28.75 7.37 7.70 # Query size 2.15k 153 296 245.11 284.79 48.90 258.32 # ==== ================== ============= ===== ====== ===== =============== # Profile # Rank Query ID Response time Calls R/Call V/M Item # ==== ================== ============= ===== ====== ===== =============== # 1 0x728E539F7617C14D 0.0011 41.0% 3 0.0004 0.00 SELECT blog_article # 2 0x1290EEE0B201F3FF 0.0003 12.8% 1 0.0003 0.00 SELECT portfolio_item # 3 0x31DE4535BDBFA465 0.0003 12.6% 1 0.0003 0.00 SELECT portfolio_item # 4 0xF14E15D0F47A5742 0.0003 12.1% 1 0.0003 0.00 SELECT portfolio_category # 5 0x8F848005A09C9588 0.0003 11.8% 1 0.0003 0.00 SELECT blog_category # 6 0x55F49C753CA2ED64 0.0003 9.7% 1 0.0003 0.00 SELECT blog_article # ==== ================== ============= ===== ====== ===== =============== # Query 1: 0 QPS, 0x concurrency, ID 0x728E539F7617C14D at byte 736 ______ # Scores: V/M = 0.00 # Time range: all events occurred at 2014-02-13 22:23:52 # Attribute pct total min max avg 95% stddev median # ============ === ======= ======= ======= ======= ======= ======= ======= # Count 37 3 # Exec time 40 1ms 352us 406us 375us 403us 22us 366us # Lock time 42 351us 103us 125us 117us 119us 9us 119us # Rows sent 25 9 1 4 3 3.89 1.37 3.89 # Rows examine 24 21 5 8 7 7.70 1.29 7.70 # Query size 47 1.02k 261 262 261.25 258.32 0 258.32 # String: # Hosts localhost # Users * # Query_time distribution # 1us # 10us # 100us ################################################################ # 1ms # 10ms # 100ms # 1s # 10s+ # Tables # SHOW TABLE STATUS LIKE "blog_article"\G # SHOW CREATE TABLE `blog_article`\G # EXPLAIN /*!50100 PARTITIONS*/ SELECT b0_.id AS id0, b0_.slug AS slug1, b0_.title AS title2, b0_.excerpt AS excerpt3, b0_.external_link AS external_link4, b0_.description AS description5, b0_.created AS created6, b0_.updated AS updated7 FROM blog_article b0_ ORDER BY b0_.created DESC LIMIT 10

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

Mysqldumpslow /var/log/mysql/mysql-slow.log

С дополнительными параметрами можно отфильтровать данные, чтобы экспортировать только нужное. Например, топ-10 запросов, отсортированных по среднему времени выполнения:

Mysqldumpslow -t 10 -s at /var/log/mysql/localhost-slow.log

Заключение

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

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

В повседневной работе приходится сталкиваться с довольно однотипными ошибками при написании запросов.

В этой статье хотелось бы привести примеры того, как НЕ надо писать запросы.

  • Выборка всех полей
    SELECT * FROM table

    При написании запросов не используйте выборку всех полей - "*". Перечислите только те поля, которые вам действительно нужны. Это сократит количество выбираемых и пересылаемых данных. Кроме этого, не забывайте про покрывающие индексы. Даже если вам на самом деле необходимы все поля в таблице, лучше их перечислить. Во-первых, это повышает читабельность кода. При использовании звездочки невозможно узнать какие поля есть в таблице без заглядывания в нее. Во-вторых, со временем количество столбцов в вашей таблице может изменяться, и если сегодня это пять INT столбцов, то через месяц могут добавиться TEXT и BLOB поля, которые будут замедлять выборку.

  • Запросы в цикле.
    Нужно четко представлять себе, что SQL - язык, оперирующий множествами. Порой программистам, привыкшим думать терминами процедурных языков, трудно перестроить мышление на язык множеств. Это можно сделать довольно просто, взяв на вооружение простое правило - «никогда не выполнять запросы в цикле». Примеры того, как это можно сделать:

    1. Выборки
    $news_ids = get_list("SELECT news_id FROM today_news ");
    while($news_id = get_next($news_ids))
    $news = get_row("SELECT title, body FROM news WHERE news_id = ". $news_id);

    Правило очень простое - чем меньше запросов, тем лучше (хотя из этого, как и из любого правила, есть исключения). Не забывайте про конструкцию IN(). Приведенный код можно написать одним запросом:
    SELECT title, body FROM today_news INNER JOIN news USING(news_id)

    2. Вставки
    $log = parse_log();
    while($record = next($log))
    query("INSERT INTO logs SET value = ". $log["value"]);

    Гораздо более эффективно склеить и выполнить один запрос:
    INSERT INTO logs (value) VALUES (...), (...)

    3. Обновления
    Иногда бывает нужно обновить несколько строк в одной таблице. Если обновляемое значение одинаковое, то все просто:
    UPDATE news SET title="test" WHERE id IN (1, 2, 3).

    Если изменяемое значение для каждой записи разное, то это можно сделать таким запросом:
    UPDATE news SET
    title = CASE
    WHEN news_id = 1 THEN "aa"
    WHEN news_id = 2 THEN "bb" END
    WHERE news_id IN (1, 2)

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

  • Выполнение операций над проиндексированными полями
    SELECT user_id FROM users WHERE blogs_count * 2 = $value

    В таком запросе индекс использоваться не будет, даже если столбец blogs_count проиндексирован. Для того, чтобы индекс использовался, над проиндексированным полем в запросе не должно выполняться преобразований. Для подобных запросов выносите функции преобразования в другую часть:
    SELECT user_id FROM users WHERE blogs_count = $value / 2;

    Аналогичный пример:
    SELECT user_id FROM users WHERE TO_DAYS(CURRENT_DATE) - TO_DAYS(registered) <= 10;

    Не будет использовать индекс по полю registered, тогда как
    SELECT user_id FROM users WHERE registered >= DATE_SUB(CURRENT_DATE, INTERVAL 10 DAY);
    будет.

  • Выборка строк только для подсчета их количества
    $result = mysql_query(«SELECT * FROM table», $link);
    $num_rows = mysql_num_rows($result);
    Если вам нужно выбрать количество строк, удовлетворяющих определенному условию, используйте запрос SELECT COUNT(*) FROM table, а не выбирайте все строки лишь для того, чтобы подсчитать их количество.
  • Выборка лишних строк
    $result = mysql_query(«SELECT * FROM table1», $link);
    while($row = mysql_fetch_assoc($result) && $i < 20) {

    }
    Если вам нужны только n строк выборки, используйте LIMIT, вместо того, чтобы отбрасывать лишние строки в приложении.
  • Использование ORDER BY RAND()
    SELECT * FROM table ORDER BY RAND() LIMIT 1;

    Если в таблице больше, чем 4-5 тысяч строк, то ORDER BY RAND() будет работать очень медленно. Гораздо более эффективно будет выполнить два запроса:

    Если в таблице auto_increment"ный первичный ключ и нет пропусков:
    $rnd = rand(1, query("SELECT MAX(id) FROM table"));
    $row = query("SELECT * FROM table WHERE id = ".$rnd);

    Либо:
    $cnt = query("SELECT COUNT(*) FROM table");
    $row = query("SELECT * FROM table LIMIT ".$cnt.", 1");
    что, однако, так же может быть медленным при очень большом количестве строк в таблице.

  • Использование большого количества JOIN"ов
    SELECT
    v.video_id
    a.name,
    g.genre
    FROM
    videos AS v
    LEFT JOIN
    link_actors_videos AS la ON la.video_id = v.video_id
    LEFT JOIN
    actors AS a ON a.actor_id = la.actor_id
    LEFT JOIN
    link_genre_video AS lg ON lg.video_id = v.video_id
    LEFT JOIN
    genres AS g ON g.genre_id = lg.genre_id

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

  • Использование LIMIT
    SELECT… FROM table LIMIT $start, $per_page

    Многие думают, что подобный запрос вернет $per_page записей (обычно 10-20) и поэтому сработает быстро. Он и сработает быстро для нескольких первых страниц. Но если количество записей велико, и нужно выполнить запрос SELECT… FROM table LIMIT 1000000, 1000020, то для выполнения такого запроса MySQL сначала выберет 1000020 записей, отбросит первый миллион и вернет 20. Это может быть совсем не быстро. Тривиальных путей решения проблемы нет. Многие просто ограничивают количество доступных страниц разумным числом. Также можно ускорить подобные запросы использованием покрывающих индексов или сторонних решений (например sphinx).

  • Неиспользование ON DUPLICATE KEY UPDATE
    $row = query("SELECT * FROM table WHERE id=1");

    If($row)
    query("UPDATE table SET column = column + 1 WHERE id=1")
    else
    query("INSERT INTO table SET column = 1, id=1");

    Подобную конструкцию можно заменить одним запросом, при условии наличия первичного или уникального ключа по полю id:
    INSERT INTO table SET column = 1, id=1 ON DUPLICATE KEY UPDATE column = column + 1

Читайте