ITDumka
Узкие места в сценариях на PHP. Часть 1: Работа с базой данных

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


Введение

Итак, давайте сперва определим о чем здесь будет идти речь. Узкие места - это части кода, которые являются основными потребителями ресурсов, используемых во время выполнения. Тоесть те части кода, на которые тратиться на много больше ресурсов, по сравнению с другими, потенциально равными по потреблению ресурсов, частями кода. Заметьте, под узкими местами я имею в виду именно части кода на PHP. Чтобы было понятнее, давайте немного отвлечемся и рассмотрим, какие узкие места еще могут присутствовать при использовании Web-приложения.

С момента, когда пользователь запросил страницу в своем чудесном браузере и до момента, когда он увидел эту страницу, результат проходит через некоторое количество узких мест. Кратко перечислим их по порядку:

  1. Первое, что делает браузер - устанавливает соединение с Web-сервером (здесь я не учитываю возможную задержку на получение ip-адреса сервера на сервере доменных имен, потому что это случается довольно редко и к PHP не имеет вообще никакого отношения). Соединение устанавливается в течение долей секунды, но если сервер слишком перегружен, то соединение может потребовать больше времени.
  2. После получения запроса сервер генерирует ответ. Это время между получением запроса Web-сервером и началом генерации ответа, называется временем обработки сценария. В это время браузер все еще ждет ответа от сервера.
  3. После генерации ответа сервером, сервер начинает передавать данные ответа браузеру. Период от начала передачи этих данных до завершения этой операции называется временем доставки. Во время генерации ответа могут происходить целые серии HTTP-запросов, например, если страница содержит множество изображений. Поэтому с каждым таким запросом может возникать определенная задержка, даже если сценарий выполнился достаточно быстро. Такая временная задержка может возникнуть в двух местах: либо в сети, используемой для передачи и получения данных с сервера, либо на самом сервере из-за его ограниченных возможностей, связанных с генерацией ответов на полученные запросы. Во втором случае причиной замедления может оказаться большая загруженность сервера, неудачная настройка модуля PHP (необязательно самого сценария) и другие выполняемые на сервере процессы. Литература предлагает искать ответы на эти вопросы в анализе использования оперативной памяти и центрального процессора.

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

Во время генерации ответа сервером происходит выполнение сценария на PHP. Этот процесс упрощенно можно описать следующей последовательностью действий:

  1. Считывание входных параметров
  2. Использование значений входных параметров и принятие решений на основании этих данных
  3. Генерация выходных данных

Действия 1 и 3 являются неотъемлемой частью модуля PHP и Web-сервера и поэтому действительно узкие места, над которыми можно поработать скапливаются в момент 2, тоесть в момент реальной обработки данных. Большинство источников выделяют три категории узких мест, приходящихся на момент 2:

  1. Недостаточная мощность оборудования. При этом используемое аппаратное обеспечение является не очень новым или работает в перегруженном режиме. Либо не грамотно или интенсивно используются программные средства, которые могут потреблять много аппаратных ресурсов. Например, используя средства библиотеки GD для PHP.
  2. Неоптимальные и неэффективные алгоритмы, приводящие, в лучшем случае, к длительному времени выполнения сценария.
  3. Внешние узкие места. Это может быть работа с базой данных, обращение к жесткому или сетевому диску, запросы HTTP или FTP, обращение к объектам через протоколы SOAP и RPC, механизмы взаимодействия с сокетами и т. п.

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


Работа с базой данных

Практически каждое Web-приложение использует базу данных. В нашем контексте производительности, база данных, в большинстве случаев, - это те 20% Парето, которые выполняют основные операции по замедлению работы скрипта. И потому как в большинстве случаев это так, то следствием из закона Амдала можно утвердить факт: максимальное уменьшение времени выполнения запросов к базе данных максимально увеличивает производительность Web-приложения на PHP.

Задержки происходят во все моменты обращения к СУБД. Основные потери связаны с длительным выполнение SQL-запросов, особенно тех, результаты которых предназначены для отображения пользователю. Многие авторитетные авторы утверждают, что если запрос длиться более половины секунды, то он должен тщательно анализироваться, а также, если суммарное время всех обращений к базе данных превышает три секунды, то это не хорошо. Конечно, эти параметры довольно приблизительны и конкретные их значения зависят от особенностей Web-приложения, но все же их можно взять за максимальный эталон.

Чтобы определить длительность какого-либо обращения к базе данных, его можно просто измерить. Для начала маленький пример:

test_db_request.php

Listing №1 (PHP)
  1. $query = 'select * from users';
  2. mysql_connect('localhost', 'root', '123');
  3. mysql_select_db('test');
  4. $StartTime = microtime(TRUE);
  5. mysql_query($query);
  6. $EndTime = microtime(TRUE);
  7. echo 'Запрос "' . $query . '" выполнен за ' . number_format($EndTime - $StartTime, 6) . ' секунд';

В данном простейшем случае используется функция microtime c параметром TRUE, которая возвращает временную метку с микросекундами. Этот сценарий выведет на экран что-то наподобие:

echo
Запрос "select * from users" выполнен за 0.000896 секунд

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

test_db_request.php

Listing №2 (PHP)
  1. $query = 'select * from users';
  2. mysql_connect('localhost', 'root', '123');
  3. mysql_select_db('test');
  4. for($i = 0; $i < 10000; $i++) {
  5.   $StartTime = microtime(TRUE);
  6.   mysql_query($query);
  7.   $TotalTime += microtime(TRUE) - $StartTime;
  8. }
  9. echo 'Запрос "' . $query . '" выполнен за ' . number_format($TotalTime/10000, 6) . ' секунд';

Результат:

echo
Запрос "select * from users" выполнен за 0.001381 секунд

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

class.timedebugger.php

Listing №3 (PHP)
  1. <?php
  2. /**
  3.  * Класс "Отладчик по времени"
  4.  */
  5. class TimeDebugger {
  6.   /**
  7.    * @var float временная переменная
  8.    */
  9.   private static $_StartTime = 0;
  10.   /**
  11.    * @var float общее время
  12.    */
  13.   private static $_AllTime = 0;
  14.   /**
  15.    * Начинает цикл измерения
  16.    */
  17.   public static function Start()
  18.   {
  19.     self::$_StartTime = microtime(TRUE);
  20.   }
  21.   /**
  22.    * Завершает цикл измерения
  23.    * @param string $DebugObject объект измерения
  24.    * @param string $Quote       комментарий
  25.    */
  26.   public static function Stop($DebugObject, $Quote = '')
  27.   {
  28.     self::$_AllTime += microtime(TRUE) - self::$_StartTime;
  29.     $InfoString  = '[TIME_DEBUGGER] ';
  30.     $InfoString .= $DebugObject;
  31.     $InfoString .= (($Quote !== '') ? ' (' . $Quote . ')' : '');
  32.     $InfoString .= ' Time: ';
  33.     $InfoString .= number_format(microtime(TRUE) - self::$_StartTime, 6);
  34.     $InfoString .= ' seconds';
  35.     self::_LogInfo($InfoString);
  36.   }
  37.   /**
  38.    * Подитоживает все циклы
  39.    */
  40.   public static function StopDebug()
  41.   {
  42.     $InfoString  = '[TIME_DEBUGGER] All Time: ';
  43.     $InfoString .=  number_format(self::$_AllTime, 6);
  44.     $InfoString .= ' seconds';
  45.     self::_LogInfo($InfoString);
  46.   }
  47.   /**
  48.    * Записывает данные в файл
  49.    * @param string $InfoString
  50.    */
  51.   private static function _LogInfo($InfoString)
  52.   {
  53.     $File = 'timedubug.txt';
  54.     $PrevInfo = @file_get_contents($File);
  55.     $PrevInfo .= date('[d-m-Y H:i:s]');
  56.     $PrevInfo .= $InfoString;
  57.     $PrevInfo .= PHP_EOL;
  58.     @file_put_contents($File, $PrevInfo);
  59.   }
  60. }
  61. ?>

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

test_db_request.php

Listing №4 (PHP)
  1. <?php
  2. date_default_timezone_set('Europe/Kiev');
  3. //какой-то код
  4. require_once 'class.timedebugger.php';
  5. //какой-то код
  6. TimeDebugger::Start();
  7. mysql_connect('localhost', 'root', '123');
  8. TimeDebugger::Stop('CONNECT');
  9. //какой-то код
  10. TimeDebugger::Start();
  11. mysql_select_db('test');
  12. TimeDebugger::Stop('SELECT DATABASE');
  13. //какой-то код
  14. $query = 'select * from users';
  15. TimeDebugger::Start();
  16. mysql_query($query);
  17. TimeDebugger::Stop('EXECUTE_QUERY', $query);
  18. //какой-то код
  19. $query = 'select id from users';
  20. TimeDebugger::Start();
  21. mysql_query($query);
  22. TimeDebugger::Stop('EXECUTE_QUERY', $query);
  23. //какой-то код
  24. $query = 'select name from users';
  25. TimeDebugger::Start();
  26. mysql_query($query);
  27. TimeDebugger::Stop('EXECUTE_QUERY', $query);
  28. //какой-то код
  29. $query = 'select name from users where id=1';
  30. TimeDebugger::Start();
  31. mysql_query($query);
  32. TimeDebugger::Stop('EXECUTE_QUERY', $query);
  33. //какой-то код
  34. TimeDebugger::StopDebug();
  35. ?>

После отработки данного скрипта в файле timedubug.txt вы увидите приблизительно такую информацию:

timedubug.txt

[16-07-2009 13:53:59][TIME_DEBUGGER] CONNECT Time: 0.002611 seconds

[16-07-2009 13:53:59][TIME_DEBUGGER] SELECT DATABASE Time: 0.000739 seconds

[16-07-2009 13:53:59][TIME_DEBUGGER] EXECUTE_QUERY (select * from users) Time: 0.001904 seconds

[16-07-2009 13:53:59][TIME_DEBUGGER] EXECUTE_QUERY (select id from users) Time: 0.001649 seconds

[16-07-2009 13:53:59][TIME_DEBUGGER] EXECUTE_QUERY (select name from users) Time: 0.000840 seconds

[16-07-2009 13:53:59][TIME_DEBUGGER] EXECUTE_QUERY (select name from users where id=1) Time: 0.001362 seconds

[16-07-2009 13:53:59][TIME_DEBUGGER] All Time: 0.008769 seconds

Как можно заметить из результатов, время тратиться не только на выполнение запросов, но и на коннект и выбор базы данных. Таким образом, мы измерили время каждого обращения скрипта к базе и определили время, затраченное на все обращения. Но мы не молодцы! Почему? Потому что погрешность измерения и определения величин задержки обращений к базе довольно велика в данном случае. Посмотрите, какая большая разница результатов в листинге №1 и листинге №2. Почти в два раза! Что ж, необходимо измерить порядочное количество обращений к скрипту. Для этого нам нужно хранить промежуточные данные, которые могут быть и не окончательными. Думаю, проще для всех будет реализовать это с помощью той же базы. Для простоты создадим одну табличку, и все данные будем сохранять в нее:

Listing №5 (SQL)
  1. CREATE TABLE `test`.`DEBUG` (
  2. `id` INT( 10 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
  3. `object_id` INT( 10 ) NOT NULL ,
  4. `object_name` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL ,
  5. `quote_id` INT( 10 ) NOT NULL ,
  6. `quote` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL ,
  7. `period` FLOAT NOT NULL ,
  8. `date_create` DATETIME NOT NULL,
  9. INDEX ( `object_id` , `quote_id` , `period` , `date_create` )
  10. ) ENGINE = MYISAM

Теперь перепишем класс, добавив в него функции сохранения собранных им данных в базу и вывод соответствующих результатов:

class.timedebugger.php

Listing №6 (PHP)
  1. <?php
  2. /**
  3.  * Класс "Отладчик по времени"
  4.  */
  5. class TimeDebugger {
  6.   /**
  7.    * Индекс объекта
  8.    * @var int
  9.    */
  10.   private static $_ObjectCounter = 0;
  11.   /**
  12.    * Индекс комментария
  13.    * @var int
  14.    */
  15.   private static $_QoutesCounter = 0;
  16.   /**
  17.    * @var float временная переменная
  18.    */
  19.   private static $_TimeStart = 0;
  20.   /**
  21.    * Массив объектов
  22.    * @var array
  23.    */
  24.   private static $_Objects = array();
  25.   /**
  26.    * Массив комментариев
  27.    * @var array
  28.    */
  29.   private static $_Quotes  = array();
  30.   /**
  31.    * Ссылка на базу данных
  32.    * @var resource
  33.    */
  34.   private static $_Db = NULL;
  35.   /**
  36.    * Начинает цикл измерения
  37.    */
  38.   public static function Start()
  39.   {
  40.     self::$_TimeStart = microtime(TRUE);
  41.   }
  42.   /**
  43.    * Завершает цикл измерения
  44.    * @param string $DebugObject объект измерения
  45.    * @param string $Quote       комментарий
  46.    */
  47.   public static function Stop($DebugObject, $Quote = '')
  48.   {
  49.     if(!isset(self::$_Objects[$DebugObject])) {
  50.       self::$_ObjectCounter++;
  51.       self::$_Objects[$DebugObject] = self::$_ObjectCounter;
  52.     }
  53.     if ($Quote == '') {$Quote = self::$_QoutesCounter;}
  54.     if(!isset(self::$_Quotes[$Quote])) {
  55.       self::$_QoutesCounter++;
  56.       self::$_Objects[$Quote] = self::$_QoutesCounter;
  57.     }
  58.     self::_CreateDb();
  59.     $Sql  = 'INSERT INTO `DEBUG`';
  60.     $Sql .= '(`object_id`, `quote_id`, `object_name`, `quote`, `period`, `date_create`)';
  61.     $Sql .= 'VALUES';
  62.     $Sql .= '( ' . self::$_ObjectCounter . ", " .
  63.         self::$_QoutesCounter . ", '" .
  64.         mysql_real_escape_string($DebugObject,  self::$_Db) . "', '" .
  65.         mysql_real_escape_string($Quote, self::$_Db) . "', " .
  66.         number_format(microtime(TRUE) - self::$_TimeStart, 6) .
  67.         ", '" . date('Y-m-d H-i-s') . "')";
  68.     mysql_query($Sql, self::$_Db);
  69.   }
  70.   /**
  71.    * Возвращает текущие результаты
  72.    * @param float $MaxTime макс. предел по времени
  73.    * @return string
  74.    */
  75.   public static function ShowResults($MaxTime = 0.5)
  76.   {
  77.     self::_CreateDb();
  78.     $Sql  = 'SELECT ';
  79.     $Sql .= '`d1`.`object_name` , ';
  80.     $Sql .= '`d1`.`quote`, ';
  81.     $Sql .= 'min( `d1`.`period` ) as min, ';
  82.     $Sql .= 'avg( `d1`.`period` ) as avg, ';
  83.     $Sql .= 'max( `d1`.`period` ) as max ';
  84.     $Sql .= 'FROM `debug` as  `d1` ';
  85.     $Sql .= 'GROUP BY `d1`.`quote_id`, `d1`.`object_name`';
  86.     $QueryResult = mysql_query($Sql, self::$_Db);
  87.     if (mysql_num_rows($QueryResult) == 0) {
  88.       $Result = 'No test data';
  89.       return;
  90.     }
  91.     $Result  = '<table border="1" align="center">';
  92.     $Result .= '<tr style="background-color: #AAAAAA;">';
  93.     $Result .= '<td>Object</td>';
  94.     $Result .= '<td>Quote</td>';
  95.     $Result .= '<td>MinTime(seconds)</td>';
  96.     $Result .= '<td>AvgTime(seconds)</td>';
  97.     $Result .= '<td>MaxTime(seconds)</td></tr>';
  98.     while($Row = mysql_fetch_assoc($QueryResult)) {
  99.       $Result .= '<tr><td>';
  100.       $Result .= htmlspecialchars($Row['object_name']);
  101.       $Result .= '</td><td>';
  102.       $Result .= htmlspecialchars($Row['quote']);
  103.       $Result .= '</td><td>';
  104.       $Result .= self::_CheckTime($Row['min'], $MaxTime);
  105.       $Result .= '</td><td>';
  106.       $Result .= self::_CheckTime($Row['avg'], $MaxTime);
  107.       $Result .= '</td><td>';
  108.       $Result .= self::_CheckTime($Row['max'], $MaxTime);
  109.       $Result .= '</td></tr>';
  110.     }
  111.     $Result .= '<tr><td  colspan="5">* Time limit(>';
  112.     $Result .= $MaxTime .')</tr></tr>';
  113.     $Result .= '</table>';
  114.     return $Result;
  115.   }
  116.   /**
  117.    * Идентифицирует нарушение по времени
  118.    * @param int $TimeValue
  119.    * @param int $MaxTime
  120.    * @return string
  121.    */
  122.   private static function _CheckTime($TimeValue, $MaxTime = 0.5)
  123.   {
  124.     return  ($MaxTime <= $TimeValue)
  125.         ? '<span style="color: #FF0000;">' .
  126.             number_format($TimeValue, 6) . '</span>*'
  127.         : number_format($TimeValue, 6);
  128.   }
  129.   /**
  130.    * Создает соединения с базой данных
  131.    */
  132.   private static function _CreateDb()
  133.   {
  134.     if (self::$_Db == NULL) {
  135.       self::$_Db = mysql_connect('localhost', 'root', '123');
  136.       mysql_select_db('test', self::$_Db);
  137.     }
  138.   }
  139. }
  140. ?>

Файл test_db_request.php оставим таким же, только удалим из него 34-ю строку - она нам более не понадобиться. Создадим еще один скрипт, который мы запустим после всех тестов, чтобы увидеть их результат:

show_test_results.php

Listing №7 (PHP)
  1. <?php
  2. require_once 'timedebugger.php';
  3. ?>
  4. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  5. "http://www.w3.org/TR/html4/loose.dtd">
  6. <html>
  7. <head>
  8. <title>TestResults</title>
  9. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  10. </head>
  11. <body>
  12. <?php
  13. echo TimeDebugger::ShowResults(0.003);
  14. ?>
  15. </body>
  16. </html>

Итак, нам осталось выполнить достаточное количество запусков test_db_request.php, что получить более правдоподобные данные. Я думаю, что вы не хотите десять тысяч раз клацать по кнопке браузера или опять что-либо писать. Обрадую вас - не только вы этого не хотели. Это за нас сделает отличная утилита, которая входит в дистрибутив Apache под названием ab. Я не буду углубляться в ее работу, просто скажу, что она может сделать то, что мы хотим. Впрочем, скажу, что она может много чего измерить. Например, сколько времени ваш скрипт реально работает с точки зрения пользователя. Ну, это сейчас не важно. Запустить наш test_db_request.php 10000 раз поможет такая строка (Windows):

cmd

c:\web\apache\bin\ab.exe -n 10000 http://localhost/test_db_request.php

После чего она постепенно выполнит все 10000 запросов, информируя вас появлением таких строк:

cmd
Benchmarking localhost (be patient)

Completed 1000 requests

Completed 2000 requests

Completed 3000 requests

Completed 4000 requests

Completed 5000 requests

Completed 6000 requests

Completed 7000 requests

Completed 8000 requests

Completed 9000 requests

Finished 10000 requests

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

Результат работы show_test_results.php

Вот теперь мы молодцы! Мы можем наблюдать более правдоподобную информацию и в более наглядном виде: минимум времени, среднее значение и максимум. При этом я поставил предел по времени в файле show_test_results.php равным 0,003 для наглядности того, какие обращения к базе более требовательны. Видно, что эти результаты отражают совсем иную картину, нежели предыдущие.

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

Я не буду вдаваться в подробности оптимизации операторов SQL и работы с базами данных - для этого полно специальной литературы, а дам лишь небольшие, но проверенные и действенные приемы, которые всегда следует использовать:

  • Выполняйте подключение к базе данных лишь однажды, если только вам действительно не нужно несколько коннектов. Для этого используйте шаблон проектирования Singleton, который я применил в примере №6.
  • Индексируйте все столбцы таблицы, по которым может осуществляться упорядочивание или фильтрация данных, тоесть те столбцы, которые сопровождаются операциями со знаками >, <, = и их комбинациями. Эти операторы чаще всего встречаются в конструкциях where и join. Также следите внимательно за особенностями СУБД и самого программного обеспечения по работе с метаданными. Вас может расслабить тот факт, что одно ПО или СУБД автоматически устанавливает индексы на вторичные ключи, например, а другое ПО или СУБД этого не сделают.
  • Старайтесь выбирать в качестве уникальных ключей именно числовые, но не строковые. Даже если вы не сомневаетесь в уникальности строковых данных, лучше добавьте числовое поле и делайте выборку по нему.
  • Минимизируйте количество запросов в коде приложения. Для этого используйте join операторы и грамотно построенные коллекции объектов. Придерживайтесь нормальной формы базы данных для возможности построения эффективных запросов к ней.
  • Применяйте операторы оптимизации SQL-запросов, специфические для СУБД.
  • После любых изменений в коде SQL-операторов, не забудьте протестировать новую скорость их выполнения.


Теперь вроде можно подвести итог:

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


Comments
By   Luge
Published   17.08.2009

Замечательная статья. всё по полочкам. А ещё главы будут?


Предлагаю продолжить тему тем, что замеры на локальном компе и на боевом сервере будут значительно отличаться, запрос запросу рознь и что для одного медленно, то для другого вполне приемлемо, так же вариант замера с помощью Slow Query Log (http://dev.mysql.com/doc/refman/5.0/en/slow-query-log.html). mysql/mysqli_connect по умолчанию при повторном подключении возвращает уже имеющуюся ссылку/объект. И ещё позужю: нормализация базы не всегда оправдана.

Published   17.08.2009

Luge

А ещё главы будут?

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

Luge

нормализация базы не всегда оправдана

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

By   Кир
Published   02.10.2009

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


Удачи!

By   Кирилл
Published   27.12.2009

И профилировать медленные запросы лучше стандартными средствами MySQl, и изучать explain-ом

Published   28.12.2009

Кирилл
И профилировать медленные запросы лучше стандартными средствами MySQl, и изучать explain-ом

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

Create comment
 
Formatting
Comment can not be edited. Please, use the button "Preview"
By
  (Enter prev char)
Comment
Categories
PHP
12
articles
Прочее
4
articles
Delphi
0
article
C/C++
0
article
C#
0
article
Java
0
article
Perl
0
article
Python
0
article
Enter
Cookie must be "ON"
Login
Password
 
Popular tags
PHP
9
articles
паттерн
5
articles
framework
5
articles
шаблон
5
articles
Template View
3
articles
Facade
3
articles
Service Stub
3
articles
Page Controller
2
articles
Singleton
2
articles
Gateway
2
articles
MySQL
2
articles
Registry
2
articles
Command
2
articles
Front Controller
2
articles
Action
2
articles
Abstract Factory
2
articles
типовое решение
2
articles
шаблоны проектирования
2
articles
Iterator
2
articles
Transaction Script
2
articles
Rambler's Top100 Правильный CSS!