Хранимая процедура mysql медленнее в 20 раз, чем стандартный запрос
У меня есть 10 таблиц с одинаковой структурой, кроме имени таблицы.
У меня есть sp (хранимая процедура), определяемая следующим образом:
select * from table1 where (@param1 IS NULL OR [email protected])
UNION ALL
select * from table2 where (@param1 IS NULL OR [email protected])
UNION ALL
...
...
UNION ALL
select * from table10 where (@param1 IS NULL OR [email protected])
Я вызываю sp со следующей строкой:
call mySP('test') //it executes in 6,836s
Затем я открыл новое стандартное окно запросов. Я просто скопировал запрос выше. Затем замените @param1 на "test".
Это выполняется в 0,321 с и примерно в 20 раз быстрее, чем хранимая процедура.
Я неоднократно менял значение параметра для предотвращения кэширования результата. Но это не изменило результат. SP примерно в 20 раз медленнее, чем эквивалентный стандартный запрос.
Пожалуйста, помогите мне разобраться, почему это происходит?
Кто-нибудь сталкивался с подобными проблемами?
Я использую mySQL 5.0.51 на Windows Server 2008 R2 64 бит.
edit: Я использую Navicat для тестирования.
Любая идея будет полезна для меня.
EDIT1:
Я только что сделал тест в соответствии с ответом Бармара.
Наконец, я изменил sp, как показано ниже, только с одной строкой:
SELECT * FROM table1 WHERE [email protected] AND [email protected]
Затем, во-первых, я выполнил стандартный запрос
SELECT * FROM table1 WHERE col1='test' AND col2='test' //Executed in 0.020s
После того, как я вызвал my sp:
CALL MySp('test','test') //Executed in 0.466s
Итак, я полностью изменил предложение, но ничего не изменилось. И я вызвал sp из командного окна mysql вместо navicat. Это дало тот же результат. Я все еще придерживаюсь этого.
my sp ddl:
CREATE DEFINER = `myDbName`@`%`
PROCEDURE `MySP` (param1 VARCHAR(100), param2 VARCHAR(100))
BEGIN
SELECT * FROM table1 WHERE col1=param1 AND col2=param2
END
И col1 и col2 объединяются с индексом.
Можно сказать, почему вы не используете стандартный запрос? Мой дизайн программного обеспечения не подходит для этого. Я должен использовать хранимую процедуру. Поэтому эта проблема очень важна для меня.
EDIT2:
Я получил информацию о профиле запроса. Большая разница связана с "отправкой строки данных" в профильной информации SP. Отправка части данных занимает% 99 времени выполнения запроса. Я делаю тест на локальном сервере базы данных. Я не подключаюсь к удаленному компьютеру.
Информация о профиле SP ![SP Profile Information]()
Информация о профиле запроса ![enter image description here]()
Я попробовал инструкцию индекса силы, как показано ниже в моей sp. Но такой же результат.
SELECT * FROM table1 FORCE INDEX (col1_col2_combined_index) WHERE [email protected] AND [email protected]
Я изменил sp, как показано ниже.
EXPLAIN SELECT * FROM table1 FORCE INDEX (col1_col2_combined_index) WHERE col1=param1 AND col2=param2
Это дало такой результат:
id:1
select_type=SIMPLE
table:table1
type=ref
possible_keys:NULL
key:NULL
key_len:NULL
ref:NULL
rows:292004
Extra:Using where
Затем я выполнил запрос ниже.
EXPLAIN SELECT * FROM table1 WHERE col1='test' AND col2='test'
Результат:
id:1
select_type=SIMPLE
table:table1
type=ref
possible_keys:col1_co2_combined_index
key:col1_co2_combined_index
key_len:76
ref:const,const
rows:292004
Extra:Using where
Я использую оператор FORCE INDEX в SP. Но он настаивает на том, чтобы не использовать индекс. Есть идеи? Я думаю, что я близок к концу:)
Ответы
Ответ 1
Возможная проблема с набором символов? Если ваш набор символов в таблице отличается от набора символов базы данных, это может вызвать проблемы.
См. отчет об ошибке: http://bugs.mysql.com/bug.php?id=26224
[12.11.2007 21:32] Mark Kubacki По-прежнему не повезло с 5.1.22_rc - ключами вступают в процедуру в течение 36 секунд и снаружи 0.12s.
[12.11.2007 22:30] Марк Кубацки После изменения кодировок на UTF-8 (особенно для двух использованных), который используется для соединение в любом случае, ключи учитываются в сохраненных процедура!
Вопрос, на который я не могу ответить, заключается в следующем: почему оптимизатор обрабатывает кодировку конверсии другим способом внутри и снаружи хранимых процедур? (В самом деле, я могу ошибаться, спрашивая об этом.)
Ответ 2
Просто догадаться:
Когда вы запускаете запрос вручную, выражение WHERE ('test' IS NULL or COL1 = 'test')
может быть оптимизировано при анализе запроса. Парсер может видеть, что строка 'test'
не является нулевой, поэтому она преобразует тест в WHERE COL1 = 'test'
. И если есть индекс на COL1
, это будет использоваться.
Однако при создании хранимой процедуры разбор происходит при создании процедуры. В то время он не знает, что будет @param
, и должен выполнить запрос как последовательное сканирование таблицы.
Попробуйте изменить процедуру:
IF @param IS NULL
THEN BEGIN
SELECT * FROM table1
UNION ALL
SELECT * FROM table2
...
END;
ELSE BEGIN
SELECT * FROM table1 WHERE col1 = @param
UNION ALL
SELECT * FROM table2 WHERE col1 = @param
...
END;
END IF;
У меня нет большого опыта работы с хранимыми процедурами MySQL, поэтому я не уверен, что все правильные синтаксисы.
Ответ 3
Интересный вопрос, потому что я люблю использовать хранимые процедуры. Причина - техническое обслуживание и принцип инкапсуляции.
Это информация, которую я нашел:
http://dev.mysql.com/doc/refman/5.1/en/query-cache-operation.html
В нем указано, что кеш запросов не используется для запросов, которые
1. являются подзапросами, которые принадлежат внешнему запросу, и
2. выполняются внутри тела хранимой процедуры, триггера или события.
Это означает, что он работает как запроектированный.
Ответ 4
Я видел это поведение, но оно не было связано с набором символов.
У меня была таблица, в которой хранились иерархические данные самореференции (родитель с детьми, а у некоторых детей были собственные дети и т.д.). Поскольку parent_id должен ссылаться на первичный идентификатор (и столбец указал ограничение на этот эффект), я не мог установить родительский идентификатор в NULL или 0 (ноль), чтобы отключить дочерний элемент от родителя, поэтому я просто ссылался на него сам по себе.
Когда я пошел, чтобы запустить хранимую процедуру для выполнения рекурсивного запроса, чтобы найти всех детей (на всех уровнях) определенного родителя, запрос занял от 30 до 40 раз больше времени для запуска. Я обнаружил, что изменение запроса, используемого хранимой процедурой, чтобы исключить родительскую запись верхнего уровня (путем указания WHERE parent_id!= Id), восстановило производительность запроса.
Хранимая процедура, которую я использую, основана на том, что показано в:
https://stackoverflow.com/info/27013093/recursive-query-emulation-in-mysql.