SQLinfo.ru - Все о MySQL

Форум пользователей MySQL

Задавайте вопросы, мы ответим

Вы не зашли.

#1 02.03.2012 09:26:06

FiMko
Активист
Откуда: Санкт-Петербург
Зарегистрирован: 18.09.2009
Сообщений: 198

Неудовлетворительная скорость работы REGEXP

Ребята, привет всем!

Помогите советом или идеей в следующей ситуации. Есть необходимость разбить входную строку на отдельные слова. Отдельными словами в первую очередь считаются части строки, разделенные запятыми, но также внутри этих подстрок отдельными словами считаются специальные символы и прочие не буквенные знаки. Таким образом, входная строка "qwe 123 @ abc$" должна быть разбита на слова:

1. "qwe"
2. " "
3. "1"
4. "2"
5. "3"
6. " "
7. "@"
8. " "
9. "abc"
10. "$"

Не вдаваясь в подробности реализации, я выполнил логику (анализ строки на предмет буквенных знаков) с использованием MySQL оператора REGEXP. А конкретнее с помощью класса символов (character class) alpha - alphabetic characters:

SELECT @in_string REGEXP '^[[:alpha:]]$+';

Первым делом я разбиваю входную строку на слова, используя пробелы. Далее, если подстрока (слово) содержит не буквенные символы ("@", "$" и т.п.), приходится проводить посимвольный анализ данного слова.

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

Был бы очень признателен, если бы вы поделились каким-либо идеями по этому поводу. Спасибо.

Отредактированно FiMko (02.03.2012 09:27:26)

Неактивен

 

#2 02.03.2012 10:51:17

rgbeast
Администратор
MySQL Authorized Developer and DBA
Откуда: Москва
Зарегистрирован: 21.01.2007
Сообщений: 3880

Re: Неудовлетворительная скорость работы REGEXP

Вы все реализуете на языке SQL? Для такой задачи проще не использовать regexp, а посимвольно пройти строку - в этом случае задача решается за один проход.

Неактивен

 

#3 02.03.2012 10:56:53

FiMko
Активист
Откуда: Санкт-Петербург
Зарегистрирован: 18.09.2009
Сообщений: 198

Re: Неудовлетворительная скорость работы REGEXP

Спасибо за помощь!

rgbeast написал:

Вы все реализуете на языке SQL?

Да. Подпрограмма анализа строк загоняет результаты работы во временную таблицу, вызывающая главная процедура затем работает с этими данными. Сама подпрограмма разбора строк на самом деле несложна, порядка 20-ти строк.

Согласен, не классический способ выполнять такого рода вычисления на стороне базы данных. Но что делать. Если я вынесу эту логику в PHP, обработаю строки и вставлю их, скажем, не во временную таблицу, а в постоянную и затем главная процедура будет основывать свою работу на "знании", что данные готовы и уже в таблице, то [1] такое "знание" сильно ухудшает инкапсуляцию компонентов системы (PHP - База данных) [2] если что-то пойдет не так при работе с данными в главной процедуре в MySQL, не возможно использовать транзакции для отмены изменений, внесенных PHP.

rgbeast написал:

Для такой задачи проще не использовать regexp, а посимвольно пройти строку - в этом случае задача решается за один проход.

Если посимвольно, то как будет выглядеть проверка "текущий символ - это буквенных знак?" ([[:alpha:]] для регулярных выражений). Плюс посимвольно - это не один проход, для строки из 1000 символов (максимальная длина) - 1000 проходов.

Отредактированно FiMko (02.03.2012 11:07:28)

Неактивен

 

#4 02.03.2012 16:08:06

rgbeast
Администратор
MySQL Authorized Developer and DBA
Откуда: Москва
Зарегистрирован: 21.01.2007
Сообщений: 3880

Re: Неудовлетворительная скорость работы REGEXP

SQL это слишком высокоуровневый язык, отсюда и REGEXP. На SQL не знаю хорошего решения. Посимвольная работа не будет быстрой, так как потребует сначала разбивать функцией substring на символы, а потом проверять опять же regexp-ом. Под одним проходом я имел в виду, что можно в одном цикле обойти все символы и проверить на буквенность, но это не решение для SQL.

Неактивен

 

#5 02.03.2012 16:45:04

FiMko
Активист
Откуда: Санкт-Петербург
Зарегистрирован: 18.09.2009
Сообщений: 198

Re: Неудовлетворительная скорость работы REGEXP

rgbeast написал:

SQL это слишком высокоуровневый язык, отсюда и REGEXP. На SQL не знаю хорошего решения. Посимвольная работа не будет быстрой, так как потребует сначала разбивать функцией substring на символы, а потом проверять опять же regexp-ом. Под одним проходом я имел в виду, что можно в одном цикле обойти все символы и проверить на буквенность, но это не решение для SQL.

Безусловно, здесь необходим какой-то компромисс в архитектуре. По факту данная задача с точки зрения разработки программы решается и в SQL весьма примитивными манипуляциями. Проблема в производительности.

Мне сложно делать в данной ситуации ставки на лучшую производительность в PHP, ибо ему (PHP) придется также взаимодействовать с базой для вставки полученных после разбиения слов. Конечно, можно вставить все разом, но остается проблема с возможным последующим rollback. И пр. и пр.

Компромисс, который я вижу сейчас: не бояться иметь в базе для строки "abc abc! abc?" дупликаты слов вида: "abc", "abc!", "abc?" и т.д. Разбиение же входной строки на слова на основе разделителя пробел работает довольно шустро. Здесь я жертвую результатами поиска, то есть, скажем для запроса "abc" будет найден только "abc", но не "abc!" или "abc?". Использовать "LIKE %abc%" не хочу из соображений производительности.

В общем задача сильно индивидуальная. rgbeast, спасибо Вам, что не оставили ее без внимания. Я думаю...

Отредактированно FiMko (02.03.2012 16:49:33)

Неактивен

 

#6 04.03.2012 21:32:37

FiMko
Активист
Откуда: Санкт-Петербург
Зарегистрирован: 18.09.2009
Сообщений: 198

Re: Неудовлетворительная скорость работы REGEXP

FiMko написал:

Я думаю...

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

PROCEDURE split_string(
    IN in_string VARCHAR(1000)
)
proc_start: BEGIN
    -- нам нужно больше места, для замены в длинных исходных строках
    DECLARE tmp VARCHAR(10000);
    -- мы будем использовать специальные теги _R_ _L_ и _C_ для знаков:
    -- "')", "('" и "," соответственно (нужны для составления выражения SQL),
    -- теги нужны во избежании конфликтов при замене знаков в исходной строке

    -- выполним замены для знаков препинания, являющихся разделителями слов
    SET tmp = REPLACE(tmp, ",", "_R__C__L_,_R__C__L_"); -- запятая
    SET tmp = REPLACE(tmp, "(", "_R__C__L_(_R__C__L_"); -- левая круглая скобка
    SET tmp = REPLACE(tmp, ")", "_R__C__L_)_R__C__L_"); -- правая круглая скобка
    SET tmp = REPLACE(tmp, "#", "_R__C__L_#_R__C__L_"); -- решетка
    SET tmp = REPLACE(tmp, "&", "_R__C__L_&_R__C__L_"); -- амперсанд
    -- таких замен может быть сколь угодно много...
   
    -- выполняем подстановку для наших тегов
    SET in_string = REPLACE(in_string, "_R_", "')");
    SET in_string = REPLACE(in_string, "_L_", "('");
    SET in_string = REPLACE(in_string, "_C_", ",");
    -- после замен возможна потеря/не доставание скобок в начале/конце выражения,
    -- удаляем лишнее или добавляем недостающее по результатам замен
    SET in_string = REPLACE(in_string, ",(''),", ",");
    SET in_string = IF(LEFT(in_string, 3) = "'),",
            RIGHT(in_string, CHAR_LENGTH(in_string) - 3), in_string);
    SET in_string = IF(LEFT(in_string, 2) = "('", in_string,
            CONCAT("('", in_string));
    SET in_string = IF(RIGHT(in_string, 3) = ",('",
            LEFT(in_string, char_length(in_string) - 3), in_string);
    SET in_string = IF(RIGHT(in_string, 2) = "')", in_string,
            CONCAT(in_string, "')"));

    -- таблица для хранения результатов
    DROP TEMPORARY TABLE IF EXISTS words;
    CREATE TEMPORARY TABLE words(
        `id` INT NOT NULL AUTO_INCREMENT,
        `word` VARCHAR(70) NOT NULL,
    PRIMARY KEY (`id`)
    ) ENGINE = MEMORY CHARACTER SET utf8 COLLATE utf8_general_ci;

    -- создаем финальное выражение и выполняем его
    SET @query = CONCAT("insert into words(word) values ",
            in_string, ";");
    PREPARE q FROM @query;
    EXECUTE q;
END

Работаем с процедурой:

mysql> call split_string('qwe##,abc');
mysql> select * from words;
+----+------+
| id | word |
+----+------+
|  1 | qwe  |
|  2 | #    |
|  3 | #    |
|  4 | ,    |
|  5 | abc  |
+----+------+

Эта процедура выполняется очень быстро.

Отредактированно FiMko (05.03.2012 10:39:45)

Неактивен

 

#7 04.03.2012 22:26:30

evgeny
Гуру
Зарегистрирован: 04.05.2009
Сообщений: 335

Re: Неудовлетворительная скорость работы REGEXP

А если в in_string накопить в таком формате :

select (@a:=1) `id`,'qwe' `word`
union all
select (@a:=@a+1) `id`,'#' `word`
union all
select (@a:=@a+1) `id`,'#' `word`
union all
...

то можно и без временной таблицы.

Отредактированно evgeny (04.03.2012 22:27:30)

Неактивен

 

#8 04.03.2012 22:37:11

FiMko
Активист
Откуда: Санкт-Петербург
Зарегистрирован: 18.09.2009
Сообщений: 198

Re: Неудовлетворительная скорость работы REGEXP

evgeny написал:

А если в in_string накопить в таком формате :

select (@a:=1) `id`,'qwe' `word`
union all
select (@a:=@a+1) `id`,'#' `word`
union all
select (@a:=@a+1) `id`,'#' `word`
union all
...

то можно и без временной таблицы.

evgeny, спасибо за ответ! Да, можно и не во временную таблицу, а в строку, вариантов много. Мне нужно именно во временную таблицу, потому что работать с этими данными предстоит главной, вызывающей процедуре, иначе в ней придется еще строку разбирать.

Неактивен

 

Board footer

Работает на PunBB
© Copyright 2002–2008 Rickard Andersson