| |
Наверное многие из вас знают, хотя бы из школьного курса математики, что любая сложная задача может быть сведена к набору простых. Тот же приём можно применить и к проблемам SQL приложения.
Для начала посмотрим пример с простым запросом. Ошибка в этом случае находится очень просто.
select * fro t1 where f1 in (1,2,1);
Я думаю, большинство читателей уже заметили в чём проблема. Если выполнить этот запрос в mysql cli ошибка стнает ещё заметней:
mysql> select * fro t1 where f1 in (1,2,1);
ERROR 1064 (42000): You have an error in your SQL syntax;
check the manual that corresponds to your MySQL server version
for the right syntax to use near 'fro t1 where f1 in (1,2,1)' at line 1
То есть это синтаксическая ошибка: опущена поледняя буква o в предикате from. Просто, не правда ли?
Возьмём пример посложнее. Это код на PHP. (Для любителей холиворов повторю, что код был подготовлен для PHP конференции в Москве. Аналогичную путаницу можно написать и на любом другом вашем любимом языке программирования.)
$query = 'SELECT * FROM t4 WHERE f1 IN(';
for ($i = 1; $i < 101; $i ++)
$query .= "'row$i,";
$query = rtrim($query, ',');
$query .= ')';
$result = mysql_query($query);
В данном случае вычленить ошибку уже труднее. А что будет в случае ещё более сложного запроса?
В случае с PHP нам поможет простой операто echo, который осуществляет вывод:
$query = 'SELECT * FROM t4 WHERE f1 IN(';
for ($i = 1; $i < 101; $i ++)
$query .= "'row$i,";
$query = rtrim($query, ',');
$query .= ')';
echo $query;
//$result = mysql_query($query);
Запустим скрипт:
$php phpconf2009_1.php
SELECT * FROM t4 WHERE f1 IN('row1,'row2,'row3,'row4,'row5,'row6,'row7,'row8,'row9,'row10,'row11,
'row12,'row13,'row14,'row15,'row16,'row17,'row18,'row19,'row20)
И тут нам ошибка становится более очевидной: пропущен закрывающий апостроф в выражении
$query .= "'row$i,";
Достаточно заменить его выражением
$query .= "'row$i',";
и запрос будет выполняться верно.
Обратите внимание, что мы выводили запрос именно в том виде, в котором его получает СУБД. Намного проще найти ошибку в готовом запросе, чем в строке, которая собирается из разных частей или содержит переменные.
Использование оператора вывода для выявления проблемного запроса простейший, но эффективный приём.
Приём 1: используйте оператор вывода для вывода запроса в том виде, в котором его получает СУБД.
К сожалению мы не всегда можем использовать echo. Например в случае поддержки существующего сложного приложения.
Посмотрим на следующий пример.
Проблема возникла на web-странице со следущим интерфейсом:
Имеющиеся системы
* Учебник
* Тест
* test2
* test2
* test2
* test2
* test2
Введите название новой системы:
<>
Описание:
<>
<Go!>
Проблема в том, что у нас каким-то образом образовалось несколько систем с одинаковым именем.
Посмотрим на код, который за это отвечает:
$system = System::factory()
->setName($this->form->get(Field::NAME))
->setDescription(
$this->form->get(Field::DESCRIPTION)
);
DAO::system()->take($system);
Что-нибудь понятно? Я думаю опытный человек может предположить в чём проблема, но в любом случае это только гипотеза, которую необходимо подтвердить или опровергнуть. Ещё менее понятно где в этом примере добавить вывод и вывод чего именно: у нас нет ни обращения к базе, ни запроса.
В PHP достаточно легко изменить код библиотеки, который отвечает за компиляцию запроса, чтобы он печатал его (запрос) в файл или на stderr перед тем, как отправить базе, но в случае с компилируемыми языками, например, с Java, библиотеку придётся перекомпилировать. Да и не всегда код библиотеки открыт.
Что же делать? В случае с MySQL мы можем применить general query log:
mysql> set global log_output='table';
Query OK, 0 rows affected (0.00 sec)
mysql> select * from mysql.general_log;
Empty set (0.09 sec)
mysql> set global general_log='on';
Query OK, 0 rows affected (0.00 sec)
Запускаем приложение.
mysql> select * from mysql.general_log order by event_time desc limit 25\G
*************************** 1. row ***************************
event_time: 2009-10-19 13:00:00
user_host: root[root] @ localhost []
thread_id: 10323
server_id: 60
command_type: Query
argument: select * from mysql.general_log order by event_time desc limit 25
...
*************************** 22. row ***************************
event_time: 2009-10-19 12:58:20
user_host: root[root] @ localhost [127.0.0.1]
thread_id: 10332
server_id: 60
command_type: Query
argument: INSERT INTO `system` (`id`, `name`, `description`) VALUES ('', 'test2', '')
...
mysql> set global general_log='off';
Query OK, 0 rows affected (0.08 sec)
В 22 строке мы видим наш запрос. Он не выглядит проблемным: обычный INSERT.
Посмотрим что мы имеем в таблице system:
mysql> select * from system;
+----+---------+------------------------------------------+
| id | name | description |
+----+---------+------------------------------------------+
| 1 | Учебник | Конструирование мужской и женской одежды |
| 2 | Тест | Тестовый геометрический набор |
| 3 | test2 | New test |
| 4 | test2 | foobar |
| 8 | test2 | |
+----+---------+------------------------------------------+
5 rows in set (0.00 sec)
Посмотрим её определение:
mysql> show create table system\G
*************************** 1. row ***************************
Table: system
Create Table: CREATE TABLE `system` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`description` tinytext NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8
1 row in set (0.09 sec)
Поле name не является уникальным. Поэтому мы можем решить проблему с дубликатами двумя способами: нужно либо сделать это поле уникальным
(alter table system add unique(name))
либо изменить приложение таким образом, чтобы эта проблема обрабатывалась не на уровне SQL.
Мы только что рассмотрели следующий приём: используйте general query log для выявления запроса, который вызывает неправильное поведение.
Приём 2: используйте general query log, если вам нужно определить какой именно запрос вызывает неправильное поведение вашего приложения.
Мы убедились, что практически всегда можно выделить запрос, вызвавший проблему в приложении. В большинстве случаев этого достаточно, чтобы найти и устранить ошибку. Это объясняет, почему большую часть данного текста я отвела именно для поиска проблемного запроса. Конечно, существуют ситуации, когда нахождением единичного запроса не отделаться. Эти ситуации будут рассмотрены отдельно. Я хотела бы обратить ваше внимание на следующие запросы:
mysql> select * from mysql.general_log;
Empty set (0.09 sec)
mysql> set global general_log='on';
Query OK, 0 rows affected (0.00 sec)
...
mysql> set global general_log='off';
Query OK, 0 rows affected (0.08 sec)
Зачем нам нужно изменять значение глобальной переменной, если мы можем влдючить general query log в конфигурационном файле?
Дело в том, что general query log сам по себе весьма дорогостоящ: он увеличивает нагрузку на сервер и так как он содержит все запросы, то вам также придётся следить за местом на диске. Начиная же с версии 5.1 его можно включать-выключать в режиме реального времени тем самым сводя дополнительную нагрузку на MySQL сервер к минимуму.
Следующий запрос включает вывод не в файл, а в таблицу. Вывод в таблицу хорош для выявления таких проблемных запросов как в последних 2-ух примерах, так как таблицу легко отсортировать: вы обращаетесь к ней также как к любой другой таблице.
mysql> set global log_output='table';
Query OK, 0 rows affected (0.00 sec)
Назад | Содержание | Вперёд |
Автор 2009 Света Смирнова COPYRIGHT © 2009 С.Смирнова и С.Ласунов sveta_гав_js-client_точка_com |
Закладки на сайте Проследить за страницей |
Created 1996-2024 by Maxim Chirkov Добавить, Поддержать, Вебмастеру |