The OpenNET Project / Index page

[ новости /+++ | форум | теги | ]

Каталог документации / Раздел "Базы данных, SQL" / Оглавление документа

Часть 1. Одиночные запросы.

Глава 1. Запрос.

Наверное многие из вас знают, хотя бы из школьного курса математики, что любая сложная задача может быть сведена к набору простых. Тот же приём можно применить и к проблемам 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




Партнёры:
PostgresPro
Inferno Solutions
Hosting by Hoster.ru
Хостинг:

Закладки на сайте
Проследить за страницей
Created 1996-2024 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру