Техника экстремального клоузконнекшинга
Рубрика: Development, MySQL | 21 April 2007, 11:13 |
Vadim Voituk
Наверное сама распостраненная профессиональная болезнь PHP-программистов – это незакрытое подключение к MySQL. Правда болезнь эта быстро самовылечивается у тех, кто, как говорится, “наступал на эти грабли”.
В принципе MySQL и сам провоцирует это, позволяя плодить соединения сотнями, а также по умолчанию небольшим временем таймаута.
Так вот представьте себе картину: пятница, 18:30, сотрудники делятся планами на выходные и ведут вялую внутреннюю переписку на тему “Так на сколько же людей заказывать столик в близжайшем пабе?”.
Пятничную идилию нарушет SMS-сообщение “MySQL: Too many connections on XXX server”, подкрепленное несколькими десятками писем от мониторинговых систем. И понеслось…
В результате достаточно оперативных поисков обнаруживается, что какая-то [censored] забывает закрыть соединение с MySQL-базой, и в момент увеличения количества посетителей портала свободных подключений попросту не осталось.
Т.к. времени разбираться в логике чужого достаточно немаленького скрипта времени небыло я прибегнул к такому трюку:
Сразу после выполнения подключения к БД:
[php]$db_link = mysql_connect(‘server.com’, ‘login’, ‘password’)[/php]
Дописываю строку:
[php]register_shutdown_function(‘mysql_close’,$db_link);[/php]
Вуаля! По завершении работы скрипта (даже аварийного) произойдет закрытие подключения к базе данных MySQL.
Осталось только добавить что, даже если незакрытые соединения не создают проблем, ОЧЕНЬ рекомендую все-таки их закрывать. А для тех кто использует InnoDB-таблицы, это делать обязательно во избежании полной потери данных. Но это уже другая история, о какой я напишу как-нибудь позже.
Tweet
Bravo! :-) Отличный хак. Надо что-нибудь такое-же придумать в контексте Java.
Я для того чтобы избежать таких проблем на production я тестирую приложение с небольшим количеством соединений в пуле (2-3). Как правило, если забыл закрыть соединение, то очень быстро получишь щелчок по носу: “Cannot get a connection, pool exhausted”. Ну а на production можно объем пула и увеличить.
Юра, в Java есть finally блок, потому финализирующая функция попросту не нужна.
А ещё есть метод finalize(), хотя его ипользование мне кажется первым “душком” плохого дизайна.
Вадим, в PHP тоже есть функция mysql_close. Но мы сейчас говорим о том случае, когда по невнимательности (или из-за кривых рук) стандартными процедурами не воспользовались.
Ладно, про Java это было так, для поддержания беседы.
В книгах по Java не пишут “Соединение с базой можно не закрывать” :) А в вот в книгах по PHP (Котеров “PHP4″, кажется) такое пишут.
Гм, ну это уже проблемы книг. :-) Скорее даже не книг, а читателей. Жуть какая :-)
Самое же интересное, что если я книгу не перепутал, то дал мне её именно ты:) Лет так 5 назад :)
Я же ее не писал, честно!
И вообще, ты еще вспомни, какие я лабы 5 лет назад писал :-)
Короче, пошел оффтоп. Если есть желание повспоминать про времена невинных студентов-программистов, жду на пиво со скумбрией (или с раками, это уж как больше нравится).
Приглашение принято :)
Прикольно. Я таким раньше не заморачивался, но теперь буду иметь ввиду.
Интересно, почему эта команда дала мне на одной хитрой странице моего проекта такой вот варнинг:
Warning: (null)(): supplied argument is not a valid MySQL-Link resource in Unknown on line 0
Саша,
Warning появился потому что ты пытаешь закрыть уже закрытое соединений.
Обойти это можно так:
[php]
function db_close($db_link) { if (!$db_link) mysql_close($db_link); // или же if (!mysql_thread_id($db_link)) mysql_close($db_link); // или же совсем ленивым и некрасивым способом @mysql_close($db_link); } //а дальше по старинке register_shutdown_function('db_close', $db_link);[/php]
Похоже на то.
Спасибо, Вадим!
Пардон, всё оказалось тривиальнее: у меня $db является не ресурсом, возвращаемым функцией mysql_connect(), а классом-враппером для работы с БД. Так что как только я вставил в register_shutdown_function() правильную переменную [$db->link], то всё заработало как надо. Но вообще подозреваю, что в PHP и без этого работало :]
Саша,
как бы банально это не звучало – я тоже не использую mysql_connect напрямую – пользуюсь PEAR::DB
В такому случае функция завершения выглядит так:
[php]
function db_close($db) { if (is_a($db,'db_common')) $db->disconnect(); }[/php]
Правда такая конструкция не спасает от закрытия закрытого по таймауту соединения.
А зачем это вообще нужно? PHP же закрывает соединение по завершению работы скрипта.
К сожалению не все так радужно как хотелось бы – очень часто соединение не закрывается, и когда таких незакрытых соединений накапливается ровно столько сколько указано в max_connections – все дальнейшие попытки покдлючиться отваливаются с ошибкой “Too many connections”
Видимо не закрывается автоматом при использовании mod_php.
При традиционном cgi запросе при завершении процесса автоматом закрылись бы все сокеты и открытые файлы, а вместе с ними и соединение с БД.
Закрывать автоматом соединение – это конечно удобно, но если у вас сессии хранятся в базе, то они уже автоматом не сохранятся (это происходит после отработки всех shutdown-функций). Придётся или “вручную” сохранять сессию до выхода с помощью session_write_close или проверять в функции сохранения наличие соединения с базой данных, и открывать её снова. Последнее – не годится, хотя, я, например, в своём фреймворке предусмотрел автоматическое соединение с базой, если какому-то другому модулю она нужна. “Ленивый” вариант с register_shutdown_function(‘session_write_close’) тоже не покатит. Там нужно ещё учитывать порядок регистрации shutdown-функций.
Считаю, что всё-таки лучше вручную закрывать всё что нужно в заданной последовательности.
Юрий,
Абсолютно с вами согласен – есть такая беда с сессиями, да соединения с БД закрывать НАДО, и делать это нужно самостоятельно.
Единственное, что мне не совсем ясно – почему нельзя сделать
[php]register_shutdown_function(‘session_write_close’)[/php]
перед тем как вызвать
[php]register_shutdown_function(‘db_close’, $db_link)[/php]
Согласно документации shoutdown-функции будут вызываться в том порядке, к котором они зарегистрированы.
Хотя я не проверял :)
Да, они действительно вызываются в таком порядке, каком зарегистрированы. Но проблема в том, что используя это во фреймворке я немогу быть уверен, что его пользователь не вызовет соединение с базой раньше, чем откроет сессию. Ну и, конечно, я немогу быть уверен, что не появится ещё какой-то модуль, которому нужно будет соединение с базой или использование сессий, и не возникнет очередная проблема с порядком shutdown-функций.
Для себя решил, что лучше отказаться от register_shutdown_function и контролировать закрытие всего что нужно самостоятельно.
Всё-таки оставил одну единственную register_shutdown_function, которая вызывается в самом начале базового класса и гарантирует вызов метода close() этого класса при любых раскладах. А уже в этом методе закрываю все ресурсы в требуемом порядке. Думаю, такой компромис будет максимально эффективным.
Да и в мануале про mysql_close() пишут, что закрывать соединение необязательно, ибо PHP сам закрывает все non-persistent соединения после завершения работы скрипта…
Vladimir,
Хотите верьте, хотите нет – но в данном случае manual врет.
О чем не раз подтверждает ошибка “Too many connections” на более-или-менее нагруженных проектах.
Vadim, я не спорю, но разработчики утверждают, что это всё ложь и провокация (соединение может не закрыться только в том случае, если Апач пал смертью храбых).
А не может быть, что нагруженные проекты используют persistent connection? Это самая распространенная ошибка до сих пор.
Думаю с использованием persistent-connection проект просто не доживает до статуса “нагруженного” :)
Когда-то на PHPConf, за кофе, обсуждалась проблема незакрытия коннекта к БД и уже тогда мнение большиства было едино:
За незакрытый коннектшн – ложить руки на твердую поверхность и стучать по ним до полного выпрямления.
Тогда я правда ещё не наступал на too many connections, но реакцию публики помню хорошо.