MySQL — Блог Валерия Леонтьева https://valera.ws Место публикации личных заметок. Технологии, управление, бизнес, жизнь Sun, 02 Feb 2014 12:11:19 +0000 ru-RU hourly 1 https://wordpress.org/?v=5.6.2 https://valera.ws/wp-content/uploads/2020/02/favicon.png MySQL — Блог Валерия Леонтьева https://valera.ws 32 32 Архитектура веб приложений: интерьер (видео-лекция) https://valera.ws/2014.02.02~web-applications-architecture-interior/ https://valera.ws/2014.02.02~web-applications-architecture-interior/#respond Sun, 02 Feb 2014 12:06:30 +0000 http://valera.ws/?p=734 Архитектура веб-приложений: интерьерРассказ о возможной внутренней архитектуре ориентированных на масштабируемость, обслуживаемость и расширяемость веб-приложений, разрабатываемых на PHP или подходящих для Веба языках программирования. Реализация компонентного подхода внутри приложения, фунционального разделения кода, введение уровней абстракции копонентов.

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

]]>
https://valera.ws/2014.02.02~web-applications-architecture-interior/feed/ 0
Архитектура веб приложений: экстерьер (видео-лекция) https://valera.ws/2014.01.18~web-applications-architecture-exterio/ https://valera.ws/2014.01.18~web-applications-architecture-exterio/#respond Sat, 18 Jan 2014 17:34:57 +0000 http://valera.ws/?p=730 Читать далее Архитектура веб приложений: экстерьер (видео-лекция) ]]> Архитектура веб-приложений: экстерьерРассказ о популярной универсальной архитектуре стека, в котором работает веб-приложение. Само приложение может быть написано на любом интерпретируемом языке с использованием любого фреймворка фреймворков. В данном случае это не важно, так как архитектура программной инфраструктуры — технологического стека, в котором оно работает, отличается мало.

Обсуждается путь запроса пользователя до и внутри сервера, его обработка и возврат ответа. Затрагиваются вопросы состояния приложения — работы с хранилищами, кэширования.

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

Смотрите в полноэкранном режиме.

]]>
https://valera.ws/2014.01.18~web-applications-architecture-exterio/feed/ 0
Форматы времени в MySQL: TIMESTAMP vs DATE[TIME] https://valera.ws/2009.09.17~time-formats-in-mysql/ https://valera.ws/2009.09.17~time-formats-in-mysql/#comments Thu, 17 Sep 2009 20:38:47 +0000 http://valera.ws/?p=351 Читать далее Форматы времени в MySQL: TIMESTAMP vs DATE[TIME] ]]> В MySQL 5 есть несколько типов данных для хранения даты и времени. Это TIMESTAMP, DATE, DATETIME, TIME и YEAR. Все они обладают своими особенностями, и выбор в пользу того или иного календарного типа должен производиться отдельно в каждой конкретной ситуации. Я хотел бы поделиться с вами результатом моего сегодняшнего миниисследования этих типов, в том числе в аспекте работы с временными зонами.Итак, все календарные типы данных подробно описаны в разделе «10.3. Date and Time Types» руководства по MySQL. А важная информация, касающаяся поддержки СУБД временных зон, расписана в разделе «9.7. MySQL Server Time Zone Support». Все следующее далее базируется на изучении руководства. В то же время, в здесь указаны лишь нюансы выбора в пользу того или иного типа, поэтому этот материал никак не заменяет мануал, но дополняет его.

Вначале краткая характеристика каждого из типов:

  • TIMESTAMP — тип данных для хранения даты и времени. Данные хранятся в виде количества секунд, прошедших с начала «эпохи Юникса». Диапазон значений: 1970-01-01 00:00:00 — 2038-12-31 00:00:00. Занимает 4 байта.
  • YEAR — тип данных для хранения года. Диапазон значений: 1901 — 2155. Занимает 1 байт.
  • DATE — тип данных для хранения даты. Диапазон значений: 1000-01-01 — 9999-12-31. Занимает 3 байта.
  • TIME — тип данных для хранения времени. Диапазон значений: −828:59:59 — 828:59:59. Занимает 3 байта.
  • DATETIME — тип данных для хранения даты и времени. Диапазон значений: 1000-01-01 00:00:00 — 9999-12-31 00:00:00. Занимает 8 байт.

Хозяйке на заметку. Интересно то, что большинство программистов полагают, что понятие «timestamp» — это и есть Unix-время. На самом же деле, timestamp — это метка, которая представляет собой последовательность символов, обозначающих дату и / или время, когда определенное событие произошло. А «время Юникса» (Unix time) или POSIX time — это количество секунд, прошедших с полуночи 1 января 1970 года по UTC. Понятие timestamp шире, чем Unix time.

Проанализировав описание типов, представленное выше, можно сделать практически все выводы о достоинствах и недостатках тех или иных типов. Все довольно просто и очевидно.

Но прежде, чем рассказать об использовании этих типов, хочу заметить, что на практике часто используется другой тип для хранения даты и времени: целочисленное значение (для хранения даты — INT (4 байта), даты и времени — BIGINT (8 байт)). Отличие использования целочисленных типов от DATE и DATETIME лишь в том, что при выводе данные не форматируются, а в вычислениях с датами и временем целые числа требуется преобразовывать в соответствующий календарный тип. Кроме того, не производится проверка на валидность представленного значения перед сохранением. Возможности сортировки сохраняются. Поэтому INT и BIGINT имеет смысл использовать в тех же случаях, как DATE и DATETIME, с целью максимизации переносимости и независимости от СУБД. Других преимуществ я не вижу, если они есть, предлагаю указать в комментах.

Использование календарных типов данный в MySQL

Начнем с самого простого — тип YEAR. Единственное его достоинство — малый размер — всего-то 1 байт. Но из-за этого действует строгое ограничение по диапазону допустимых значений (тип может хранить только 255 разных значений). Мне сложно представить практическую ситуацию, когда может потребоваться хранить года строго в диапазоне от 1901 до 2155. Кроме того, тип SMALLINT (2 байта) дает диапазон, достаточный в большинстве ситуаций для хранения года. А экономить 1 байт на строке в таблице БД в наше время смысла нет.

Типы DATE и DATETIME можно объединить в одну группу. Они хранят дату или дату и время с довольно широким диапазоном допустимых значений, независимую от установленной на сервере временной зоны. Их использование определенно имеет практический смысл. Но если требуется хранить даты исторических событий, уходящие в прошлое за Нашу эру, придется выбрать другие типы данных. Для хранения дат неких событий, потенциально выходящих за рамки диапазона типа TIMESTAMP (дни рождений, даты выпуска продуктов, избрания президентов, запуски космических ракет и т.д.), отлично подойдут эти типы. При использовании этих типов нужно учитывать один важный нюанс, но об этом ниже.

Тип TIME можно использовать для хранения промежутка времени, когда не нужна точность меньше 1 секунды, и промежутки времени меньше 829 часов. Добавить тут больше нечего.

Остался самый интересный тип — TIMESTAMP. Рассматривать его надо в сравнении с DATE и DATETIME: TIMESTAMP тоже предназначен для хранения даты и/или времени происхождения неких событий. Важное отличие между ними в диапазонах значений: очевидно, что TIMESTAMP не годится для хранения исторических событий (даже таких, как дни рождений), но отлично подходит для хранения текущих (логирование, даты размещения статей, добавления товаров, оформления заказов) и предстоящих в обозримом будущем событий (выходы новых версий, календари и планировщики и т.д).

Основное удобство использования типа TIMESTAMP состоит в том, что для столбцов этого типа в таблицах можно задавать значение по умолчанию в виде подстановки текущего времени, а так же установки текущего времени при обновлении записи. Если вам требуется эти возможности, то с вероятностью 99% TIMESTAMP — именно то, что вам нужно. (Как этоделать, смотрите в мануале.)

Не стоит бояться того, что с приближением к 2038 году ваш софт перестанет работать. Во-первых, до этого времени вашим софтом, скорее всего, просто перестанут пользоваться (особенно версиями, которые пишутся сейчас). Во-вторых, с приближением к этой дате разработчики MySQL обязательно что-нибудь придумают для сохранения работоспособности вашего софта. Все решится так же хорошо, как проблема Y2K.

Итак, тип TIMESTAMP используем для хранения дат и времени свершения событий нашего времени, а DATETIME и DATE — для хранения дат и времени свершения исторических событий, или событий глубокого будущего.

Диапазоны значений — это важное отличие между типами TIMESTAMP, DATETIME и DATE, но не главное. Главное то, что TIMESTAMP хранит значение в UTC. При сохранении значения оно переводится из текущего временной зоны в UTC, а при его чтении — во время текущей временной зоны из UTC. DATETIME и DATE хранят и выводят всегда одно и то же время, независимо от временных зон.

Временные зоны устанавливаются в СУБД MySQL глобально или для текущего подключения.Последнее можно использовать для обеспечения работы разных пользователей в разных временных зонах на уровне СУБД. Все значения времени физически будут храниться в UTC, а приниматься от клиента и отдаваться клинту — в значениях его временной зоны. Но только при использовании типа данных TIMESTAMP. DATE и DATETIME всегда принимают, хранят и отдают одно и то же значение.

Функция NOW() и ее синонимы возвращают значение времени в текущей временной зоне пользователя.

Учитывая все эти обстоятельства, необходимо быть крайне внимательными при изменении временной зоны в пределах подключения к серверу и использовании типов DATE и DATETIME. Если надо хранить дату (например, дату рождения), то никаких проблем не будет. Дата рождения в любой зоне одинаковая. Т.е. если вы родились 1 января в 0:00 UTC/GMT+0, то это не значит, что в Америке будут праздновать ваш день рождения 31 декабря. Но если вы решите хранить время события в столбце DATETIME, то тут уже построить работу с пользовательскими временными зонами на уровне СУБД просто не выйдет. Поясню на примере:

Пользователь X работает в зоне UTC/GMT+2, Y — в зоне UTC/GMT+3. Для соединений пользователей с MySQL установлена соответствующая (у каждого своя) временная зона. Пользователь размещает сообщение на форуме, нас интересует дата написания сообщения.

Вариант 1: DATETIME. Пользователь X пишет сообщение в 14:00 UTC/GMT+2. Значение в поле «дата» сообщения подставляется как результат выполнения функции NOW() — 14:00. Пользователь Y считывает время написания сообщения и видит те же 14:00. Но у него в настройках стоитзона UTC/GMT+3, и он думает, что сообщение было написано не только что, а час назад.

Вариант 2: TIMESTAMP. Пользователь X пишет сообщение в 14:00 UTC/GMT+2. В поле «дата» попадает результат выполнения функции NOW() — в данном случае — 12:00 UTC/GMT+0. ПользовательY считывает время написания сообщения и получает (UTC/GMT+3)(12:00 UTC/GMT+0) = 15:00 UTC/GMT+3. Все получается ровно так, как мы хотим. И главное — пользоваться этим крайне удобно: для поддержки пользовательских временных зон не нужно писать никакой код приведения времени.

Возможности подстановки текущего времени и работы с временными зонами в типе TIMESTAMP настолько весомы, что если вам в неком логе надо хранить дату без времени, все равно стоит использовать TIMESTAMP, вместо DATE, не экономя 1 байт разницы между ними. При этом на «00:00:00» просто не обращать внимания.

Если же вы не можете использовать TIMESTAMP из-за относительно малого диапазона его значений (а обычно это 1—2 случая против 10—15 в базе сайта), придется использовать DATETIME и аккуратно его корректировать значения в нужных местах (т.е. при записи в это поле переводить дату в UTC, а при чтении — во время в зоне считывающего пользователя). Если вы храните только дату, то скорее всего не важно, какая у вас временная зона: новый год все празднуют 1 января по локальному времени, ничего переводить тут не понадобится.

]]>
https://valera.ws/2009.09.17~time-formats-in-mysql/feed/ 3
Локальная офисная версия сайта https://valera.ws/2008.12.06~office-website/ https://valera.ws/2008.12.06~office-website/#respond Sat, 06 Dec 2008 14:05:24 +0000 http://valera.ws/?p=213 Читать далее Локальная офисная версия сайта ]]> Часто в компаниях есть штатные редакторы, которые работают с некими сайтами этой, принадлежащими ей — наполняют их контентом, модерируют и т.д. Обычно редакторы вместе с обычными пользователями пользуются одной версией сайта, которая располагается на хостинге в сети. Но это не оптимально, так как лишний трафик (прежде всего HTML-код страниц, рисунки, css) гуляет с сервера на офис, занимая внешний канал компании и тратя трафик веб-сервера. При мдленном внешнем канале тратится так же и время редакторов.

В идеале, в данном случае передавать необходимо только добавляемые и получаемые данные, так как весь антураж может быть сохранен и доступен в пределах офиса локально.

Для решения этой задачи можно использовать общую базу данных, которая будет располагаться на хостинге. На сервере в локальной сети поднять сайт, указать для подключения к СУБД параметры доступа к база на удаленном сервере. Таким образом, у вас будут 2 одинаковых сайта, параллельно работающих с одним источником данных.

В принципе это обычная схема для балансировки нагрузки на фронтэнд (web-сервер). Практику много фронтэндов — один бэкэнд (база) применяют довольно часто, а следующий этап разделения нагрузки (когда уже СУБД не выдерживает напора) — кластеризация серверов баз данных. Но это лирическое отступление.

Следуюет учесть три нюанса. Во-первых, так как сервера у нас теперь два, время на них может быть разное. Следовательно при записи в базу временные показатели будут для серверов расходиться и может возникнуть путаница. Чтобы этого избежать, проще всего брать время из СУБД. Делать это надо естественно 1 раз за запрос (кэшировать), а не при каждой необходимости (при условии, что запрос выполняется не более 1 секунды; если больше, значит ваша система нуждается в оптимизации). Таким образом время на всех серверах будет синхронизировано по времени на сервере с СУБД.

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

Решить эту проблему можно через подключение к СУБД по защищенному SSL-протоколу. Популярные СУБД поддерживают SSL (MySQL, PgSQL). Подключение клиента к серверу через SSL поддерживают родные C API этих СУБД, и PHP MySQL/PgSQL API тоже. Но SSL не поддерживается PDO (во всяком случае я не нашел информацию о поддержке), который в настоящее время очень популярен и который следует использовать. По этому вариант подключения к базе через SSL отпадает.

Остается другой, довольно простой для использования и сложный для настройки вариант — перегон трафика между офисом и хостингом через VPN. Сам VPN я уже описывал в своем блоге. После поднятия VPN-тоннеля необходимо прописать роут на IP вашего сервера (того сервера, где находится СУБД) через виртуальный интерфейс VPN-соединения. Таким образом весь трафик между PHP и СУБД будет ходить по безопасному зашифрованному каналу.

Третий нюанс заключается в кэшировании. Часто кэш некоторых элементов заводят пожизненно (или на долгое время), а сбрасывается он только при изменении данных редактором. В этом случае, при внесении изменений на одном из серверов, кэш не сбросится на втором и на неопределенный срок там останется неактуальная информация. Эта проблема решается через изменение стратегии кэширования, либо использование общего сервера кэширования и выходит за рамки данной статьи. Если именно этот вопрос для вас остается актуальным, ищите информацию в гугле, т.к. та же самая проблема существует при балансировке нагрузки путем «размножения» фронэндов.

Когда все проблемы решены, все что остается делать — это следить за тем, чтобы версии скриптов на серверах в сети и на офисе были одинаковыми, чтобы не повредить базу.

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

]]>
https://valera.ws/2008.12.06~office-website/feed/ 0
Bigmir.net собирается потеснить TUT.BY https://valera.ws/2008.07.23~bigmir-belarus/ Wed, 23 Jul 2008 13:00:14 +0000 http://valera.ws/?p=78 Читать далее Bigmir.net собирается потеснить TUT.BY ]]> По сообщению ИнтернетУА компания «Бигмир-Интернет», которая является частью крупнейшего медиа-холдинга KP Media и владеет крупнейшим украинским порталом Bigmir.net, готовит выход на белорусский рынок.

Небольшая цитата:

Свою экспансию в Белоруссию Бигмир начал еще в апреле — с запуска баннерной сети BBN.BY. И хотя белорусский корпоративный домен Бигмира mir-internet.by все еще закрыт паролем, компания не теряет времени даром — в течение месяца готовится выкатить новый сервис — рейтинг сайтов. Примечателен тот факт, что в первую очередь «Бигмир» пытается привлечь внимание белорусских вебмастеров, запуская узконаправленные веб-сервисы. Однако в долгосрочной перспективе «Бигмир» твердо намерен потеснить белорусского монополиста TUT.BY.

Сейчас на белорусском рынке Интернет-сервисов самый известный портал TUT.BY. Он занимает явно монополистическое плоложение. По просмотрам страниц и известности бренда портал имеет подавляющее преимущество перед конкурентами (точных цифр к сожалению нет). А конкурируют с ним только два портала: open.by и более-менее известный date.by. Остальные крупные и популярные ресурсы байнта являются тематическими.

На TUT.BY приходится солидная доля поиска белорусов. По статистике известного «счетчика» Беларуси, доля использования поисковика tut.by в Байнете 21.56%, опережают его только Google и Яндекс, в то время как ближайший конкурент all.by (портал open.by) имеет лишь 2.07%.

Некоторые профессионалы белорусской IT-сферы не довольны поведением на рынке портала TUT.BY, определяя его политику как «перетягивании всего одеяла на себя». Портал насыщен разного рода сервисами, которые целенаправленно продвигает во взаимосвязи, тем самым не оставля шансов узконаправленным ресурсам на реальную раскрутку. Кроме того, в последнее время не наблюдается динамичного развития портала. Зато на страницах портала появилось обильнейшее наличие рекламы. Сказывается отсутствие реальной конкуренции.

Учитывая прочные позиции bigmir.net на Украине и опыт «Бигмир-Интернет» в развтии этого проекта, можно рассчитывать, что появление этого нового игрока в Беларуси обусловит положительные тенденции и бурное развитие Интернет-рынка.

]]>
Поиск в MySQL. Часть 3 «FULLTEXT IN BOOLEAN MODE» https://valera.ws/2008.04.15~fulltext-in-mysql/ https://valera.ws/2008.04.15~fulltext-in-mysql/#comments Tue, 15 Apr 2008 14:53:10 +0000 http://valera.ws/2008.04.15~fulltext-in-mysql/ Читать далее Поиск в MySQL. Часть 3 «FULLTEXT IN BOOLEAN MODE» ]]> Поиск с учетом русской морфологии

Поиск в MySQL. Часть 3 «FULLTEXT IN BOOLEAN MODE»

В первой части рассказа о поиске в MySQL рассказывается про использование полнотекстного индекса FULLTEXT. При поиске неких ключевых слов в большом массиве текста, хранящегося в БД, без использования индекса не обойтись. Однако родные возможности полнотекстового поиска в СУБД MySQL не обеспечиват функционал для поиска с учетом русской морфологии. Решение этой проблемы описывалось во второй части «Поиск с учетом русской морфологии». И вот недавно в описанном алгоритме обнаружился большой недостаток. Что это за недостаток и как с ним бороться и описано в этой статье.

Для начала хочу заметить один нюанс. Не так давно вышел релиз СУБД PostgreSQL 8.3. И одной из важнейших его особенностей стало то, что полнотекстный поиск, причем еще и с поддержкой русской морфологии, включен в ядро СУБД. Теперь не надо использовать различные плагины и примочки, чтобы пользоваться возможностями полнотекстного о поиска. А возможности эти на порядок мощнее и щире возможностей fulltext-поиска в MySQL. По-этому, если поиск информации в базе для вашего проекта является одной из центральных «фишек», стоит смотреть в сторону Postgres.

Если же у вас нет выбора СУБД, или требования к поиску невысокие, можно пользоваться и моим решением на базе MySQL. Только необходимо учесть поправки для поискового запроса, описанные ниже.

По делу. При использовании конструкции MATCH … AGAINST СУБД ищет указанные в запросе ключевые слова в индексе, если подходящий найден, или в указанном поле данных. Поиск может проходить в трех разных режимах: в натуральном (обычном) и логическом (boolean mode). Общее между этими режимами то, что в обоих случаях фраза поиска, переданная в AGAINST будет разделена на слова. И при поиске будут находиться записи, в которых встречается хотя бы одно из указанных слов.

Например, при указании фразы «трудовой договор» будут найдены все строки, в которых встречается слово «трудовой» и все строки со словом «договор». На практике такой подход оказывается неприемлемым, так как выдает слишком много лишних результатов. Особенно ярко выражается проблема, когда одно слово очень популярное и находится в огромном количестве строк. В этом случае уточняющее слово практически не имеет смысла. И релевантность тут не спасет. Популярное слово может встретиться в строке 50 раз. В этом случае строка получит релевантность значительно выше, чем строка в которой оба слова встретились по одному разу.

В результате поиск по такому принципу в большом массиве информации становится неюзабельным. Обойти проблему можно с помощью второго режима поиска — поиска в логическом режиме. Его преимущество в том, что с помощью некоторых специальных операторов можно управлять значимостью и ролью слов в запросе, а также искать по фразам.

Для включения логического режима используется синтаксис: MATCH (…) AGAINST (‘…’ IN BOOLEAN MODE);

Оператор пишется в кавычках непосредственно перед ключевым словом, к которому он относится. Операторы, которые воспринимает анализатор запроса:

  • — (дефис, символизирующий «минус»). Этот оператор указывает, что данное ключевое слово не должно содержаться в поле (NOT);
  • + указывает, что слово должно обязательно содержаться в запросе (AND);
  • > и < соответственно увеличивают и уменьшают вес слова при подсчете релевантности;
  • ( и ) группируют слова для применения к ним оператора
  • ~ имеет смысл, схожий с «минусом», но оно слово не исключает, а просто уменьшает релевантность строки, в которой слово встретилось;
  • * — оператор усечения, ставится в конце слова и имеет классический смысл (например, слов*);
  • » (кавычка). В кавычки берутся фразы, по которым требуется искать целиком.

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

Чтобы обеспечить точный поиск и релевантность вывода, передадим в запрос ключевые слова в обработанном виде. Первой частью пустим в кавычках фразу, составленную из слов, которые ввел пользователь (естественно, предварительно отфильтровав спец-символы). Установим перед ней оператор повышения релевантности «>». Далее через пробел открываем скобки. В них нужно указать слова, полученные от морфологического движка. Перед скобками ставим оператор уменьшения релевантности. Слова пишутся через пробел, а перед каждым словом ставится оператор «+».

Для наглядности приведем пример. При поиске по фразе «отпуск за работу» запрос будет выглядеть так: … MATCH (…) AGAINST (‘>»отпуск за работу» <(+отпуск +работу)’ IN BOOLEAN MODE) …

Кроме этого, из-за логического режима поиска необходимо вручную указать порядок вывода (сортировку) найденных строк. Для этого добавляем в выборку еще один столбец, который определяется так выражением MATCH … AGAINST с псевдонимом. Причем оно должно быть идентично выражению в блоке WHERE. Тогда СУБД будет выполнять поиск только один раз. Ну а в конец запроса нужно дописать, что сортировка идет по полю (через псевдоним) в порядке убывания.

Итак, теперь пример полного результирующего запроса. Поиск по фразе «отпуск за работу» в таблице table в полях text и text_index, по которым был предварительно создан полнотекстный индекс:

SELECT SQL_CALC_FOUND_ROWS *,
MATCH (text,text_index) AGAINST (‘>»отпуск за работу» <(+отпуск +работа)’ IN BOOLEAN MODE) AS rel
FROM table
WHERE MATCH (text,text_index) AGAINST (‘>»отпуск за работу» <(+отпуск +работа)’ IN BOOLEAN MODE)
ORDER BY rel DESC LIMIT 0, 10

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

]]>
https://valera.ws/2008.04.15~fulltext-in-mysql/feed/ 14
Поиск в MySQL. Часть 2 «Поиск с учетом русской морфологии» https://valera.ws/2007.09.05~morpho_search_in_mysql/ https://valera.ws/2007.09.05~morpho_search_in_mysql/#comments Wed, 05 Sep 2007 08:23:10 +0000 http://valera.ws/2007.09.05~morpho_search_in_mysql/ Читать далее Поиск в MySQL. Часть 2 «Поиск с учетом русской морфологии» ]]> Поиск с учетом русской морфологииПоиск в MySQL. Часть 2 «Поиск с учетом русской морфологии»

В этой статье описывается идея создания поиска в базе MySQL на основе индекса FULLTEXT с учетом русской морфологии. Никаких модулей для PHP или MySQL и других программ устанавливать на хостинг не нужно. И это важное преимущество данного алгоритма. Алгоритм подойдет для сайтов с малой и средней нагрузкой. Для крупных порталов, конечно, следует искать более скоростные и производительные решения (например, Sphinx). Мой вариант поиска работает значительно быстрее поиска на основе регулярных выражений и подойдет для большого количества web-проектов. Кстати, и не только web :)

Основан алгоритм на уже готовых решениях. Движок для  работы с морфологией – phpMorphy. Это морфологический модуль, написанный на PHP. Он основан на словарях проекта AOT. Оба продукта распространяются под лицензией LGPL.

Вкратце про проект phpMorphy.

Возможности:

  • Получение базовой формы слова
  • Получение всех словоформ слова
  • Получение грамматических характеристик для каждой словоформы
  • Предсказание ненайденных в словаре слов

Характеристики:

Анализ производится по словарю, размер словаря для русского языка ~4Mb. Скорость работы ~700 слов в секунду в нормальном режиме и ~1000 слов с загруженным в память словарем (без предсказания).

Подключение движка морфологии.

Для начала, конечно, нужно скачать модуль phpMorphy с официального сайта. В архиве находятся два каталога: dicts – словари АОТ, src – исходники phpMorphy. Разместите эти два каталога на сервере (желательно в общем каталоге phpMorphy). Далее для работы с модулем необходимо подключить в скрипте сайта файл src/common.php. Например так:

require_once(’phpmorphy/src/common.php’);

Библиотека подключена. Теперь вкратце о ее настройках. Порядок создания объекта:

$dict_bundle = new phpMorphy_FilesBundle(’phpmorphy/dicts’, ‘rus’); // создается объект словарей, //передается путь к каталогу со словарями. В данном случае используется русский словарь.

// Создаем объект словарей

$morphy = new phpMorphy($dict_bundle, array(’storage’ => PHPMORPHY_STORAGE_MEM, ‘with_gramtab’ => false, ‘predict_by_suffix’ => true, ‘predict_by_db’ => true));

Подробнее об опциях можно почитать в README к скрипту, а вот про ‘storage’ стоит упомянуть и тут. Это важная настройка.

phpMorphy поддерживает следующие варианты работы со словарями:

  • PHPMORPHY_STORAGE_FILE – использует файловые операции (fread, fseek) для доступа к словарям. Этот вариант самый медленных, но требует меньше всего памяти.
  • PHPMORPHY_STORAGE_SHM – загружается словари в общую память (используя расширение PHP shmop). Этот режим предпочтителен.
  • PHPMORPHY_STORAGE_MEM – загружает словари в память каждый раз, когда phpMorphy инициализируется. Этот режим используется, когда расширение shmop не включено. Скорость у PHPMORPHY_STORAGE_SHM и PHPMORPHY_STORAGE_MEM одинаковая.

Так как в начале статьи оговорено, что никаких дополнительных расширений мы не используется, в примере указано ’storage’ => PHPMORPHY_STORAGE_MEM. Если с такими настройками ваш поиск «вылетает» с ошибками о недостатке памяти, или злостный хостер ругается, что вы расходуете память больше положенного, включите’storage’ => PHPMORPHY_STORAGE_FILE.

Сразу хочу обратить ваше внимание, что алгоритм поиска будет правильно работать только при правильно настроенной локали (PHP setlocale) и кодировке в БД.

Изменения в таблицах БД.

Теперь поговорим об организации условий для поиска в БД. Напомню, что поиск у нас основан на индексах, что определяет высокую скорость выполнения поисковых запросов (подробнее в части 1).

Допустим, у нас есть некая таблица сообщений, по которой будет осуществляться поиск. В таблице есть поля: message_id, message_text,  message_date. Искать нужно по полю message_text. Для организации поиска с учетом морфологии необходимо создать еще одно поле. Назовем его message_words_index. И как раз для этого поля необходимо создать индекс FULLTEXT вместе с полем message_text.

Пример создания такой таблицы:

CREATE TABLE `fn_messages` (`message_id` int(10) unsigned NOT NULL auto_increment,
`message_text` text NOT NULL,   `message_date` timestamp
NOT NULL default CURRENT_TIMESTAMP,
`message_words_index` text NOT NULL,
PRIMARY KEY  (`message_id`),
FULLTEXT KEY `fn_messages_words_index` (`message_text`,`message_words_index`)
) ENGINE=MyISAM;

Таблица готова к работе с полнотекстовым морфологическим поиском.

Внесение данных в таблицу.

При добавлении записей в таблицу, их нужно готовить для проведения поиска. Первые три поля заполняются как обычно, а в последнее вносятся слова сообщения в обработанной начальной форме. Что бы получить начальную форму слов, используется phpMorphy. Создадим функцию function Words2BaseForm($text), в которую передается исходный текст сообщения, а возвращает она в виде строки набор слов в начальных формах. Например:

Передано Получено
При реорганизации предприятия в мае менялись формы и условия контрактов. Когда набирали новых сотрудников с ними контракт не заключали, ждали пока новые формы не будут разработаны. Прошло 3 месяца. Как нам правильно заключить контракты с вновь принятыми людьми, т.к. задним числом мы заключить не можем (с введением новых контрактов изменились и условия оплаты)? БЫТЬ ВВЕДЕНИЕ ЖДАТЬ ЗАДНИЙ ИЗМЕНИТЬСЯ КОГДА ЧЕЛОВЕК МЕНЯТЬСЯ НАБИРАТЬ ОПЛАТА ФОРМА ПОКА РАЗРАБОТАТЬ СОТРУДНИК ЧИСЛО ВНОВЬ ЗАКЛЮЧИТЬ КОНТРАКТ КОНТРАКТОВЫЙ МЕСЯЦ МОЧЬ НОВЫЙ НОВОЕ ПРАВИЛЬНЫЙ ПРАВИЛЬНО ПРЕДПРИЯТИЕ УСЛОВИЕ ПРИНЯТЬ ПРИНЯТЫЙ ПРОЙТИ ПРОШЛЫЙ РЕОРГАНИЗАЦИЯ ЗКЛЮЧАТЬ

Содержимое столбца «Получено» будет помещено в поле message_words_index. Скачать функцию function Words2BaseForm($text) можно здесь.

Поиск данных.

При получении запроса на поиск для обработки фразы поиска используется другая функция, созданная для работы с phpMorphy: function Words2AllForms($text). Принимая разделенные пробелом слова для поиска, она возвращает эти слова во всех их морфологических формах. Таким образом, в полях message_text и message_words_index будут искаться все формы слов, а результаты выведутся по релевантности. Можно создавать индекс и искать только по полю message_words_index, но в этом случае вывод по релевантности будет нарушен.

Замечу, что создание дополнительного поля message_words_index необходимо еще и потому, что система phpMorphy может привести к начальной форме даже те слова, которых нет в словарях. Она это делает по анализу слова в соответствии с
правилами морфологии языка. А вот обратная система не сработает. Поэтому поиск по начальной форме даст более точные результаты.

Итак, передаем поисковый запрос в функцию function Words2AllForms($text), а возвращенный ей результат помещаем в запрос на выборку (вместе с изначальным условием для соблюдения релевантности). Скачать функцию function Words2AllForms($text) можно здесь.

Запрос имеет следующий вид:

SELECT SQL_CALC_FOUND_ROWS * from messages WHERE MATCH (message_text, message_words_index) AGAINST (изначальный_текст, обработанный_текст) LIMIT 0, 10;

Подсветка найденных слов.

Не буду писать про организацию подсветки много слов. Скажу лишь, что она основана на словоформах, полученных из function Words2AllForms($text). Скачать PHP-код подсветки найденных слов можно здесь.

Заключение.

Протестировать данный алгоритм поиска можно на сайте http://kadrovik.by.

Сама идея поиска «распространяется» под лицензией BSD и принадлежит мне (Валерию Леонтьеву). При перепечатке статьи ссылка на эту страницу обязательна. Обращаю внимание, что использованные в алгоритме библиотеки распространяются по лицензии LGPL.

Ссылки.

Продолжение! Внимание, при использовании описанной технологии обязательно прочитайте продолжение статьи. Правки в этот материал решено не вносить. Поиск в MySQL. Часть 3 «FULLTEXT IN BOOLEAN MODE»

]]>
https://valera.ws/2007.09.05~morpho_search_in_mysql/feed/ 20
MySQL vs PostgreSQL https://valera.ws/2007.09.04~postgresql-vs-mysql/ https://valera.ws/2007.09.04~postgresql-vs-mysql/#comments Tue, 04 Sep 2007 14:28:41 +0000 http://valera.ws/2007.09.04~postgresql-vs-mysql/ Читать далее MySQL vs PostgreSQL ]]> MySQL vs PostgreSQLНеделю назад я инициировал обсуждение на одном сайтике на тему миграции с MySQL на PostgreSQL. В процессе обсуждения плавно сменили тему на сравнение этих двух популярных СУБД. В результате мне показалось, что почитать это обсуждение будет интересно и полезно многим программистам. В обсуждении участвовал известный программист PHP, автор двух книг по PHP, разработчик сервиса moikrug.ru Дмитрий Котеров.

Вот обсуждение, немного обработанное с вырезанными лишними постами.

from MySQL to PostgreSQL

Валерий Леонтьев, программист PHP/MySQL

Вопрос тем, кто уже прошел такой путь: на сколько сложен переход для программиста, на сколько велики отличия, сколько % запросов обычно надо переписать для сайта на MySQL. Все рассматривается в контексте программирвоания на PHP.

Илья Бутыльский, web-developer

Не сильно сложно. Взвесьте все «за» и «против». Может и не стоит переходить.

Валерий Леонтьев, программист PHP/MySQL

Уже взвесил. Скорость постгри при нагрузке в разы выше, чем у MySQL. При этом, предел отказоустойчивости тоже выше. Т.е. переводить мелкие проекты смысла нет, а вот крупные стоило бы.

Михаил Талисов, Магистрант, Java EE developer

У меня тоже возникала год назад необходимость переводить приложение с MySQL на PostgreSQL. Особых трудностей не возникло.

Но все же некоторые вещи пересмотреть пришлось: в PostgreSQL нет автоинкрементных полей (для автоматического инкремента ПК используется специальный тип SERIAL). И для хранения значения счетчика используются специальные конструкции — sequence. Для получения текущего значения счетчика используется нечто вроде:

«select currval(‘task_id_seq’)»

Вроде больше особых отличий нету, остальное — детали.

Дмитрий Кадыков, web-программист Интернет-гипермаркета 003.ru

Мне приходилось делать обратную операцию: перевести часть кода с Postgre на MySQL. Пришлось заменить TO_CHAR(date,’MM.YYYY’) на DATE_FORMAT(date, ‘%m.%Y\’), и в принципе всё.

Валерий Леонтьев, программист PHP/MySQL

Re: Дмитрий Кадыков

А по какой причине так пришлось сделать, если не секрет?

Антон Полумисков, PHP программист

А на каком MySQL проект находится сейчас? У постгри приемуществ конечно хватает, но может проще будет перейти на MySQL5, если проект конечно не под 5 работает )), он и быстрее чем предшественники и возможностей стало больше. Но и самое главное — возникнет значительно меньше проблем с переходом.

Валерий Леонтьев, программист PHP/MySQL

Re: Антон Полумисков

Понимаешь, тут дело такое: если бы не 5 версия MySQL, то я бы уже давно перешел на ту же PostgreSQL или что-то другое. Версии до 5 — это полупустые СУБД. Там кроме тормозючести еще и половины фишек нет, которые в других СУБД давно есть и являются стандартом.

Евгений Ненаглядов, Вэб-разработчик

А мне из мускула не хватает REPLACE. В постгресе сие реализуется только триггером на INSERT, а я еще не освоил программирование процедур.

Переводил довольно крупный проект. Нудно, но не критично.

Из-за особенностей работы с автоинкреметными полями, реализовал замену функции mysql_insert_id().

Дмитрий Котеров, МойКруг.ру: сооснователь, рук. разработки

Скорость постгри при нагрузке в разы выше, чем у MySQL

Мой опыт показывает, что это совершенно неверное заявление. Все в точности да наоборот. Вы какой тип таблиц используете? Если MyISAM, то все понятно — они под нагрузкой плохо живут, если операции записи производятся сравнительно часто. Попробуйте InnoDB, они работают примерно так же, как таблицы в PostgreSQL (версионник с блокировкой строк). И попробуйте еще настройки MySQL потюнить — от них многое зависит.

Для крупных высоконагруженных проектов важное значение имеет репликация. В MySQL она встроенная и, главное, работает отлично — практически нет запаздывания при обновлении реплики. Для PostgreSQL есть Slony, однако у него: (а) раз в 10 бОльший «барьер на вход» (т.е. если с репликацией MySQL Вы в продакшене освоитесь, скажем, за неделю, то в Slony — за 10 недель), и (б) конструктивно значительный лаг при обновлении реплик, а значит, Вам придется строить приложение с учетом этого лага, что не так-то просто (встроенных средств в Slony для отслеживания этого лага [пока] нет).

Кроме того, если Вы рассчитываете с переходом на PostgreSQL обеспечить лучшую сохранность данных, то для обеспечения высокой производительности совместно с высокой надежностью нужно обязательно использовать аппаратный дисковый контроллер со встроенной кэш-памятью записи значительного размера и батарейкой (а такие контроллеры недешевы). Без аппаратного решения Вы «упретесь» в операцию fsync, которая, собственно, и обеспечивает сохранность данных, и которая несовместима с интенсивным обновлением данных без аппаратного решения.

Так что, если у Вас проект прекрасно работает на MySQL, возможностей MySQL ему хватает, то я бы категорически не рекомендовал переходить на PostgreSQL. PostgreSQL нужен для действительно сложных проектов, когда возможностей MySQL (даже 5-й версии) не хватает, когда приходится применять нереляционный подход, — например, для МойКруг. По крайней мере, в стратегическом (долгосрочном) плане повышения быстродействия при переходе с MySQL на PostgreSQL Вы точно не добьетесь.

Валерий Леонтьев, программист PHP/MySQL

Re: Дмитрий Котеров

Спасибо за столь подробный ответ. Мое «заявление» на самом деле основывалось не на личном опыте (как понятно из темы — я с постгри не работал), а не статьях других программистов. Последняя статья на эту тему, которую я прочитал была опубликована в журнале «Системный администратор» кажется за август (я не обратил внимания на номер). И как раз там у них по результатам опытов разница получилась в разы.

В MySQL медленнее работают сложные запросы: с джойнами и подзапросами. На этой почве у меня даже развилась некая фобия таких запросов. А если эх будет много в скрипте, да плюс нагрузка пиковая большая, то MySQL может и в ступор впасть. Один раз я такой ступор уже наблюдал. Правда там причина была в том, что проект был написан неграмотно (не мной) и не стояли индексы. Когда я открыл show processes, я там наблюдал кучу сложных висящих запросов. И загрузка ЦП деманом мускуля 99%. Так как времени на оптимизацию не было, да и двиг этот скоро уйдет в корзину, я не менял запросы, но просто сделал для них «правильные индексы», проблема частично решилась. Таких ступоров больше не было. И вот сложилось по статьям у меня впечатление, что с постгри такого не будет, а если и будет, то при много большей нагрузке. Тип таблиц в данном скрипте действительно MyISAM.

Александр Лещинский, Мизантропичный админ

Re: Дмитрий Котеров

Мой опыт показывает, что это совершенно неверное заявление.

нечасто я соглашаюсь с Дмитрием, но тут поддержу из всех стволов. Безмозглое неграмотное мудачье, не знающее матчасти, делает «абы как» своими руками, растущими из жопы, а потом удивляется «а шо, нам обещали что будет все шоколадно, а все совсем не так»? Любой инструмент, даже самый хороший, не отменяет необходимости думать… головой!!! а не тем, чем привыкли так называемые «работники» — очком.

Re: Валерий Леонтьев

Про журнальчик этот вобще цензурных слов нет… большего сборища ламерья я еще не встречал… Писать и проектировать надо аккуратно, чтобы не накрывал БП

Дмитрий Кадыков, web-программист Интернет-гипермаркета 003.ru

Re: Валерий Леонтьев

Да просто новому начальнику не понравилось, что одна часть проекта работает по Postgre, а всё остальное — под MySQL. Кстати, сразу же хотелось бы задать вопрос всем: может ли считаться оправданным совмещение двух СУБД в рамках одного проекта и в каких случаях стоит так делать?

Валерий Леонтьев, программист PHP/MySQL

Re: Дмитрий Котеров

Кстати, как я понял, Дмитрий Котеров повсеместно предпочитает использовать InnoDB. А как с fulltext-поиском? Используете ли Вы его? Или ищите по каким-то другим алгоритмам?

Валерий Леонтьев, программист PHP/MySQL

Re: Дмитрий Кадыков

Логически поразмышляв, пришел к выводу, что совмещение СУБД оправдано быть не может. Обычно, из 2х СУБД одна лучше. Тогда зачем делить, логичнее все делать на той, которая лучше в данной ситуации. Возможно, деление оправдано в крайне редких случаях, когда разные элементы проекта требуют функционал, доступный только в разных СУБД. Не знаю бывает ли такое вообще, но если и бывает, то крайне редко (может 1 сайт из 1000).

Александр Лещинский, Мизантропичный админ

Re: Валерий Леонтьев

Логически поразмышляв, пришел к выводу, что совмещение СУБД оправдано быть не может.

А практики говорят, что резоны могут быть…

Валерий Леонтьев, программист PHP/MySQL

Ну так у любого правила есть исключения. Я и пример даже привел.

Евгений Неверов, Я — человек!

Если же вернуться к сути вопроса, то структуру из одной БД нужно будет перенести заранее ручками (проще это), а вот данные скорее всего перенесутся без проблем.

Был у меня один проект, который я переносил с MySQL на PostgreSQL, так вот там по ходу дела пришлось сменить структуру таблиц. Чтобы импортировать данные я написал около 20 регулярных выражений, которые преобразовывали INSERT-запросы к нужному формату. Но в результате всё получилось очень хорошо.

Ответить

Виктор Куряшкин, Semantic Web specialist, Sun Microsystems

На постгрес с мускула имеет смысл переходить, если вы будете использовать возможности постгреса.

Просто «переписать запросы», сделать автоинкремент сиквенсом и все — это не аргумент.

Если вы не планируете усложнять логику на уровне субд — то точно переходить не стоит. Просто провозитесь и получите прибавку к производительности 5%, а то и потеряете в некоторых случаях.

Дмитрий Котеров, МойКруг.ру: сооснователь, рук. разработки

В MySQL медленнее работают сложные запросы: с джойнами и подзапросами

Не совсем так. Скорость выполнения запроса зависит во многом от того, «попадает» ли он, грубо говоря, в индексы. Если запрос «не попал» в индексы в MySQL и «не попал» в PostgreSQL, неверно говорить, что «в PostgreSQL этот запрос будет работать быстрее» — он в обоих случаях работает недопустимо долго.

Другое дело, что в PostgreSQL мощный планировщик и оптимизатор запросов (правда, он при этом и очень сложен, а также часто «ошибается», т.е. на его приструнение и изучение нужно тратить много времени). Кроме того, в PostgreSQL есть всякие там Hash Join и т.д. Так что, действительно, у написанного «от балды» сложного многотабличного запроса в PostgreSQL вероятность выполниться быстро выше, чем у того же запроса в MySQL. Но речь-то сейчас не о вероятностях, а о том, что запросы нужно строить грамотно, смотреть план их выполнения и «подгонять» друг под друга запрос, его план и индексы. А если все подогнанно, то и там, и там выполняется быстро, и на передний план выходит уже скорость апдейтов и простота репликации.

Кстати, как я понял, Дмитрий Котеров повсеместно предпочитает использовать InnoDB. А как с fulltext-поиском? Используете ли Вы его? Или ищите по каким-то другим алгоритмам?

Собственно, на MойКруге PostgreSQL во многом как раз из-за того, что в нем есть отличный полнотекстовый поиск, который и не снился MySQL. (А в 8.3 он будет еще лучше.) В InnoDB, к сожалению, полнотекстовых индексов и правда не бывает. Но я слышал, что есть масса внешних решений для индексации таблиц — в этом случае индекс строится с задержкой и по нему не так удобно искать, конечно, но — это лучше, чем ничего. Лично мне с ними работать не приходилось.

Опять же, если строить систему с нуля, и если в системе планируется много апдейтов данных, то лучше идти в сторону как раз раздельного хранения полнотекстового индекса и данных. Ну или хотя бы считать полнотекстовый индекс асинхронно (с задержкой) — как сделано здесь.

Насчет систем с двумя различными СУБД — почему бы и нет? Мы, например, используем и MySQL тоже (как раз там, где мало селектов, но много инсертов и апдейтов). Да, на администрирование дополнительной базы должно тратиться больше времени, однако тут есть два «но»:

1. Админ, работая с двумя базами сразу, здорово повышает свою квалификацию. Кроме того, появляется возможность сравнивать те или иные вещи.

2. MySQL очень неприхотлива и нетребовательна в администрировании (по моим приблизительным оценкам — там раз в 10 меньше подводных камней, чем в PostgreSQL), поэтому затраты на администрирование и MySQL тоже крайне малы.

Поучаствовать в обсуждении можно здесь: http://moikrug.ru/topics/216367527/

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

]]>
https://valera.ws/2007.09.04~postgresql-vs-mysql/feed/ 3
Вызов хранимых функций в запросе SELECT СУБД MySQL https://valera.ws/2007.08.29~mysql_function/ https://valera.ws/2007.08.29~mysql_function/#comments Wed, 29 Aug 2007 14:48:29 +0000 http://valera.ws/2007.08.29~mysql_function/ Читать далее Вызов хранимых функций в запросе SELECT СУБД MySQL ]]> Сегодня на работе столкнулся с таким вопросом: при запросе SELECT, где в качестве одного или нескольких полей данных вызывается пользовательская хранимая функция, будет ли эта функция вызываться для записей, подходящих под запрос, но не вошедших в LIMIT?

Т.е., например такой запрос:

SELECT id, my_code(code) FROM table1 LIMIT 0, 10; /* сколько раз выполниться функция my_code, если записей в таблице 20? */

Конечно, логично предположить, что оптимизатор заметит, что вызов функции не имеет смысла для строк, которые не выдаются клиенту. Но ведь функция может содержать SQL-команды, вносящие изменения в базу (например, добавлять запись в таблицу code_log при каждом вызове функции my_code() ). Т.е. все же некоторый смысл в этом найти можно. Как же реализовали этот момент разработчики? Я склонялся к варианту, что вызов будет только для возвращаемых строк.

Решил проверить на практике. Использовал пример с insert’ом внутри хранимой функции. И предположения подтвердились. Функция вызывается ровно столько раз, сколько строк в передается клиенту. В примере выше функция будет вызвана 10 раз.

]]>
https://valera.ws/2007.08.29~mysql_function/feed/ 1
SQL_CALC_FOUND_ROWS в MySQL https://valera.ws/2007.08.29~sql_calc_found_rows/ https://valera.ws/2007.08.29~sql_calc_found_rows/#comments Wed, 29 Aug 2007 12:25:11 +0000 http://valera.ws/2007.08.29~sql_calc_found_rows/ Читать далее SQL_CALC_FOUND_ROWS в MySQL ]]> PHP & MySQLSQL_CALC_FOUND_ROWS в MySQL

Начиная от версии 4.0 в СУБД MySQL появилась достаточно удобная возможность подсчета количества всех подходящих под запрос записей, когда количество записей ограничивается LIMIT’ом. При работе с поиском в БД, а так же при выборках из таблиц с большим количеством записей такой функционал просто необходим.

Синтаксис. В запросе SELECT перед списком столбцов необходимо указать опцию SQL_CALC_FOUND_ROWS.  Вот начало описания синтаксиса конструкции SELECT.

SELECT
[ALL | DISTINCT | DISTINCTROW ]
[HIGH_PRIORITY]
[STRAIGHT_JOIN]
[SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT]
[SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]
select_expr, … …

Таким образом, выполняя запрос SELECT SQL_CALC_FOUND_ROWS СУБД подсчитает полное число строк, подходящих под условие запроса, и сохранить это число в памяти. Естественно, имеет смысл запрос SELECT SQL_CALC_FOUND_ROWS только при использовании ограничения (LIMIT). Сразу после выполнения запроса на выборку для получения количества записей нужно выполнить еще один SELECT-запрос: SELECT FOUND_ROWS ();. В результате MySQL вернет одну строку с одним полем, в котором и будет храниться число строк.

Пример самих запросов:

> SELECT SQL_CALC_FOUND_ROWS * FROM tbl_name WHERE number > 100 LIMIT 10;
> SELECT FOUND_ROWS();

Первый запрос вернет (выведет) 10 строк таблицы tbl_name, для которых выполняется условие number > 100. Второй вызов команды SELECT возвратит количество строк, которые возвратила бы первая команда SELECT, если бы она была написана без выражения LIMIT. Хотя при использовании команды SELECT SQL_CALC_FOUND_ROWS, MySQL должен пересчитать все строки в наборе результатов, этот способ все равно быстрее, чем без LIMIT, так как не требуется посылать результат клиенту.

Пример запросов из PHP:

$result = mysql_query(«SELECT SQL_CALC_FOUND_ROWS * FROM table1 LIMIT 0, 10», $link);
while ($row = mysql_fetch_assoc($result))
{
var_dump($row);
}

$result = mysql_query(«SELECT FOUND_ROWS()», $link);
$num_rows = mysql_result($result, 0);
echo «$num_rows Rows\n»;

В результате выполнения кода при условии, что $link указывает на открытое соединение с СУБД, PHP выведет 10 строк из таблицы table1 , а затем целочисленное значение количества строк, соответствующих запросу (без учета LIMIT).

В запросах с UNION SQL_CALC_FOUND_ROWS может вести себя двояко из-за того, что LIMIT может появляться в нескольких местах. Счет строк может вестись для индивидуальных SELECT-запросов, или же для всего запроса после объединения.

Цель SQL_CALC_FOUND_ROWS для UNION состоит в том, что он должен вернуть количество строк, которые будут возвращены без глобального LIMIT. Условия применения SQL_CALC_FOUND_ROWS с UNION перечислены ниже:

  • Ключевое слово SQL_CALC_FOUND_ROWS должно указываться в первом операторе SELECT.
  • Значение FOUND_ROWS() будет точным только при условии применения UNION ALL. Если указано UNION без ALL, происходит исключение дубликатов, и значение FOUND_ROWS() будет лишь приблизительным.
  • Если в UNION не присутствует LIMIT, то SQL_CALC_FOUND_ROWS игнорируется и возвращается количество строк во временной таблице, которая создается для выполнения UNION.

SQL_CALC_FOUND_ROWS, SQL_COUNT_FOUND_ROWS, SQL_ FOUND_ROWS

]]>
https://valera.ws/2007.08.29~sql_calc_found_rows/feed/ 6
Поиск в MySQL. Часть 1 «Что такое хорошо, и что такое плохо?» https://valera.ws/2007.08.29~fulltext_search_in_mysql/ https://valera.ws/2007.08.29~fulltext_search_in_mysql/#comments Wed, 29 Aug 2007 06:46:46 +0000 http://valera.ws/2007.08.29~fulltext_search_in_mysql/ Читать далее Поиск в MySQL. Часть 1 «Что такое хорошо, и что такое плохо?» ]]>

PHP & MySQLПоиск в MySQL. Часть 1 «Что такое хорошо, и что такое плохо?»

Каждый программист PHP в свое время сталкивается с организацией поиска на сайте по какому-то набору информации или по всему сайту. Хороший программист все данные хранит в базе данных, следовательно, и искать будем там же. Речь в статье будет идти только о СУБД MySQL. Именно на ней работает подавляющее большинство сайтов в Internet, и она доступна на любом хостинге.

Отвлекаясь от темы, замечу, что у некоторых других СУБД существуют технологии поиска (в частности FULLTEXT) производительнее и мощнее, чем в MySQL. К тому же в MySQL FULLTEXT-поиск есть только в MyISAM engine и отсутствует в более скоростном и надежном InnoDB. И это конечно играет не в пользу MySQL. Но малые и средние системы вполне под силу обслужить  и MySQL. Причем, если поиск на малых сайтах можно реализовать даже на базе оператора LIKE, то для сайтов среднего уровня этот подход не подойдет из-за медленности и «грузности» выполнения запросов. В этом случае используется так называемый FULLTEXT-поиск, т.е. поиск на базе индексов FULLTEXT.

Про FULLTEXT-поиск уже написано множество разных статей, тема хорошо освещена в руководствах (как на английском, так и на русском) и книгах. Целью данной статьи не является обучить вас работать с FULLTEXT и рассказать про его синтаксис. Цель этой статьи – рассказать и показать, насколько FULLTEXT-поиск ускоряет запросы, а так же раскрыть еще один нюанс поиска. Итак, приступим.

С изучением данной темы на практике я столкнулся еще давно при создании поискового механизма для сайта со средним уровнем посещаемости, но с большим архивом многосимвольных статей. Это был сайт издательства журналов. Мой старый движок многолетней давности перестал справляться со своими задачами. Как результат, после запроса на поиск посетитель мог ждать по 30-60 секунд результатов поиска в исключительных случаях, что недопустимо. Старый поиск базировался на сложном запросе с «ручным» вычислением релевантности. Да еще и с учетом морфологии (на regexp). Конечно, такой запрос вводил сервер в ступор при поиске по большому количеству записей. Вот тогда мне и пришлось осваивать технологию полнотекстового поиска.

Основное преимущество FULLTEXT-поиска – скорость выполнения. Т.к. он базируется на индексации, скорость выполнения запросов может в 10 раз превышать скорость выполнения запросов на базе LIKE. Важное преимущество так же в том, что результаты автоматически сортируются по релевантности (с помощью ORDER BY можно «заказать» другую сортировку).

Основная проблема FULLTEXT-поиска в MySQL – отсутствие поддержки морфологии русского языка. Он был «заточен» для поиска на англоязычных ресурсах. Даже список «общеупотребительных» (подробнее в руководствах, ссылки выше) слов был составлен на английском. Поэтому русский язык был, да и сейчас остается в полном пролете :) (во второй части статьи описано, как создать в MySQL FULLTEXT-поиск с учетом русской морфологии).

Итак, создаем нашу табличку, определяем в ней для поля, по которому будем искать, индекс FULLTEXT. Заполняем данными. Пример (в индексе сразу два поля):

CREATE TABLE table1 ( field1 VARCHAR (255), field2 TEXT, FULLTEXT (field1, field2) )

Теперь, что бы осуществить поиск в таблице, нужно выполнить запрос. Например:

SELECT * FROM table WHERE MATCH field1, field2 AGAINST (строка для поиска);

По этому запросу будут возвращены строки, где встречаются слова «строка» и «поиска» (слово «для» будет опущено из-за длины; подробнее в руководствах).

Входящий поисковый запрос в PHP не забудьте обработать. Например, так (источник):

$search = substr($search, 0, 64);

$search = preg_replace(”/[^\w\x7F-\xFF\s]/”, ” “, $search);

$good = trim(preg_replace(”/\s(\S{1,2})\s/”, ” “, ereg_replace(” +”, “  “,”
$search “)));

$good = ereg_eplace(” +”, ” “, $good);

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

Теперь о практической стороне вопроса. Вернемся к моему сайту, на котором я изучал FULLTEXT-поиск. Именно на нем в свое время я замерял скорости выполнения запросов. Измерения производились на локальном сервере без нагрузки. Поэтому учтите, что при серьезной нагрузке на сервер, разрыв в значениях увеличивается пропорционально, в разы.

В таблице «articles» (MyISAM) находились статьи. Текст лежал в поле «art_full» (mediumtext). Средний размер статей я не считал, но их содержимое было в среднем примерно как эта статья. А самих записей было порядка 1000. Сервер, обслуживающий БД, был достаточно медленный. Но нам это даже на руку: четче видны различия во времени и меньше влияние сторонних факторов. Все запросы выполнялись несколько раз, и высчитывалось среднее время выполнения.

Итак, запросы:

SELECT * FROM `articles` WHERE `art_full` like ‘%рынок%’ or `art_short` like ‘%рынок%’ or `art_name` like ‘%рынок%’ order by art_id;

/* 1. Result : “Query OK, 70 rows affected (0,38 sec)” */

SELECT SQL_CALC_FOUND_ROWS * FROM `articles`
WHERE `art_full` like ‘%рынок%’ or `art_short` like ‘%рынок%’ or `art_name` like
‘%рынок%’ order by art_id limit 10;

/* 2. Result : “Query OK, 10 rows affected (0,28 sec)” */

SELECT SQL_CALC_FOUND_ROWS * FROM `articles`
WHERE `art_full` like ‘%рынок%’ or `art_short` like ‘%рынок%’ or `art_name` like
‘%рынок%’ order by art_id limit 60, 10;

/* 3. Result : “Query OK, 10 rows affected (0,28 sec)” */

SELECT * FROM `articles` WHERE MATCH(`art_full`,`art_short`, `art_name`)
AGAINST(’рынок’) order by art_id;

/* 4. Result : “Query OK, 70 rows affected (0,13 sec)” */

SELECT SQL_CALC_FOUND_ROWS * FROM `articles`
WHERE MATCH(`art_full`,`art_short`, `art_name`) AGAINST(’рынок’) order by art_id
limit 10;

/* 5. Result : “Query OK, 10 rows affected (0,03 sec)” */

SELECT SQL_CALC_FOUND_ROWS * FROM `articles`
WHERE MATCH(`art_full`,`art_short`, `art_name`) AGAINST(’рынок’) order by art_id
limit 60, 10;

/* 6. Result : “10 rows affected (0,06 sec)” */

SELECT * FROM `pg_articles` WHERE MATCH(`art_full`,`art_short`, `art_name`)
AGAINST(’рыно*’ IN BOOLEAN MODE) order by art_id;

/* 7. Result : “Query OK, 125 rows affected (0,17 sec)” */

SELECT SQL_CALC_FOUND_ROWS * FROM `pg_articles` WHERE MATCH(`art_full`,`art_short`,
`art_name`) AGAINST(’рыно*’ IN BOOLEAN MODE) order
by art_id limit 10;

/* 8. Result : “Query OK, 10 rows affected (0,05 sec)” */

SELECT SQL_CALC_FOUND_ROWS * FROM `pg_articles` WHERE MATCH(`art_full`,`art_short`,
`art_name`) AGAINST(’рыно*’ IN BOOLEAN MODE) order
by art_id limit 60, 10;

/* 9. Result : “10 rows affected (0,08 sec)” */

Проанализировать запросы детально Вы можете самостоятельно, отличия выделены цветом. Но обратите внимание, какой выигрыш во времени получается при использовании FULLTEXT вместо регулярных выражений (LIKE). Это видно при сравнении времени выполнения 1 и 4 запросов. А вот и тот нюанс, который упоминался выше. Это SQL_CALC_FOUND_ROWS. Использование этой конструкции в запросах MySQL так же дает немалый прирост в скорости. Об этом подробнее.

При организации поиска на сайте всегда пишется, сколько элементов найдено всего, а сами результаты разбиты на страницы. Кстати, для того, чтобы подсчитать число страниц, так же надо знать общее количество найденных элементов. Решений у этой задачи 3:

1) Просто получить все найденные строки от MySQL целиком (те поля, которые нужно вывести). Т.к. в поиске обычно кроме заголовка выводится кусочек текста, где были найдены слова поиска, придется получать от MySQL самое объемное поле. Учитывая, что мы выбираем все найденные записи, это сильно скажется на скорости передачи данных в PHP и скорости их обработки, а так же на количество потребленной PHP памяти. Последнее в некоторых случаях может вообще прервать обработку скрипта из-за отсутствия свободной памяти. Конечно, такой подход вовсе недопустим.

2) Выполнить два запроса. В первом получить только ID записей, которые были найдены. Затем в PHP высчитать, какие записи нам нужны (учитывая, какая страница запрошена и сколько записей на одной странице). Затем, выполнить второй запрос и получить именно эти записи. Такой подход исключает нехватку памяти (если количество записей на странице в пределах разумного) и работает быстрее первого при большом количестве объемных записей. Он допустим, но это не лучший путь.

3) А лучший путь – это, конечно, использование SQL_CALC_FOUND_ROWS. В этом случае так же выполняется два запроса, но от MySQL принимается меньшее количество записей. Выгоду от применения SQL_CALC_FOUND_ROWS можно увидеть на примерах 1 и 2 запросов, а так же 3, 4 и 5. Но стоит отметить, что чем ближе запрашиваемая страница к последней странице в результатах поиска, тем больше требуется времени на выполнение запроса с SQL_CALC_FOUND_ROWS.

Подробнее описанию возможностей MySQL, связанных с SQL_CALC_FOUND_ROWS, посвящена отдельная статья.

Поиск в MySQL. Часть 2 «Поиск с учетом русской морфологии»

]]>
https://valera.ws/2007.08.29~fulltext_search_in_mysql/feed/ 1