Техника экстремального клоузконнекшинга

Рубрика: 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-таблицы, это делать обязательно во избежании полной потери данных. Но это уже другая история, о какой я напишу как-нибудь позже.

Комментариев: 25

25 Responses to “Техника экстремального клоузконнекшинга”

Комментарии:

  1. Juriy

    Bravo! :-) Отличный хак. Надо что-нибудь такое-же придумать в контексте Java.
    Я для того чтобы избежать таких проблем на production я тестирую приложение с небольшим количеством соединений в пуле (2-3). Как правило, если забыл закрыть соединение, то очень быстро получишь щелчок по носу: “Cannot get a connection, pool exhausted”. Ну а на production можно объем пула и увеличить.

  2. vadim

    Юра, в Java есть finally блок, потому финализирующая функция попросту не нужна.
    А ещё есть метод finalize(), хотя его ипользование мне кажется первым “душком” плохого дизайна.

  3. juriy

    Вадим, в PHP тоже есть функция mysql_close. Но мы сейчас говорим о том случае, когда по невнимательности (или из-за кривых рук) стандартными процедурами не воспользовались.
    Ладно, про Java это было так, для поддержания беседы.

  4. vadim

    В книгах по Java не пишут “Соединение с базой можно не закрывать” :) А в вот в книгах по PHP (Котеров “PHP4″, кажется) такое пишут.

  5. juriy

    Гм, ну это уже проблемы книг. :-) Скорее даже не книг, а читателей. Жуть какая :-)

  6. vadim

    Самое же интересное, что если я книгу не перепутал, то дал мне её именно ты:) Лет так 5 назад :)

  7. Juriy

    Я же ее не писал, честно!
    И вообще, ты еще вспомни, какие я лабы 5 лет назад писал :-)

    Короче, пошел оффтоп. Если есть желание повспоминать про времена невинных студентов-программистов, жду на пиво со скумбрией (или с раками, это уж как больше нравится).

  8. vadim

    Приглашение принято :)

  9. Скакунов Александр

    Прикольно. Я таким раньше не заморачивался, но теперь буду иметь ввиду.

  10. Скакунов Александр

    Интересно, почему эта команда дала мне на одной хитрой странице моего проекта такой вот варнинг:

    Warning: (null)(): supplied argument is not a valid MySQL-Link resource in Unknown on line 0

  11. vadim

    Саша,
    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]

  12. Скакунов Александр

    Похоже на то.
    Спасибо, Вадим!

  13. Скакунов Александр

    Пардон, всё оказалось тривиальнее: у меня $db является не ресурсом, возвращаемым функцией mysql_connect(), а классом-враппером для работы с БД. Так что как только я вставил в register_shutdown_function() правильную переменную [$db->link], то всё заработало как надо. Но вообще подозреваю, что в PHP и без этого работало :]

  14. vadim

    Саша,
    как бы банально это не звучало – я тоже не использую mysql_connect напрямую – пользуюсь PEAR::DB
    В такому случае функция завершения выглядит так:
    [php]

    function db_close($db) {
      if (is_a($db,'db_common'))
        $db->disconnect();
    }
    

    [/php]
    Правда такая конструкция не спасает от закрытия закрытого по таймауту соединения.

  15. Аркадий

    А зачем это вообще нужно? PHP же закрывает соединение по завершению работы скрипта.

  16. vadim

    К сожалению не все так радужно как хотелось бы – очень часто соединение не закрывается, и когда таких незакрытых соединений накапливается ровно столько сколько указано в max_connections – все дальнейшие попытки покдлючиться отваливаются с ошибкой “Too many connections”

  17. TomB

    Видимо не закрывается автоматом при использовании mod_php.
    При традиционном cgi запросе при завершении процесса автоматом закрылись бы все сокеты и открытые файлы, а вместе с ними и соединение с БД.

  18. Юрий Апостол

    Закрывать автоматом соединение – это конечно удобно, но если у вас сессии хранятся в базе, то они уже автоматом не сохранятся (это происходит после отработки всех shutdown-функций). Придётся или “вручную” сохранять сессию до выхода с помощью session_write_close или проверять в функции сохранения наличие соединения с базой данных, и открывать её снова. Последнее – не годится, хотя, я, например, в своём фреймворке предусмотрел автоматическое соединение с базой, если какому-то другому модулю она нужна. “Ленивый” вариант с register_shutdown_function(‘session_write_close’) тоже не покатит. Там нужно ещё учитывать порядок регистрации shutdown-функций.

    Считаю, что всё-таки лучше вручную закрывать всё что нужно в заданной последовательности.

  19. Vadim Voituk

    Юрий,
    Абсолютно с вами согласен – есть такая беда с сессиями, да соединения с БД закрывать НАДО, и делать это нужно самостоятельно.
    Единственное, что мне не совсем ясно – почему нельзя сделать
    [php]register_shutdown_function(‘session_write_close’)[/php]
    перед тем как вызвать
    [php]register_shutdown_function(‘db_close’, $db_link)[/php]
    Согласно документации shoutdown-функции будут вызываться в том порядке, к котором они зарегистрированы.
    Хотя я не проверял :)

  20. Юрий Апостол

    Да, они действительно вызываются в таком порядке, каком зарегистрированы. Но проблема в том, что используя это во фреймворке я немогу быть уверен, что его пользователь не вызовет соединение с базой раньше, чем откроет сессию. Ну и, конечно, я немогу быть уверен, что не появится ещё какой-то модуль, которому нужно будет соединение с базой или использование сессий, и не возникнет очередная проблема с порядком shutdown-функций.

    Для себя решил, что лучше отказаться от register_shutdown_function и контролировать закрытие всего что нужно самостоятельно.

  21. Юрий Апостол

    Всё-таки оставил одну единственную register_shutdown_function, которая вызывается в самом начале базового класса и гарантирует вызов метода close() этого класса при любых раскладах. А уже в этом методе закрываю все ресурсы в требуемом порядке. Думаю, такой компромис будет максимально эффективным.

  22. Vladimir

    А в вот в книгах по PHP (Котеров “PHP4″, кажется) такое пишут.

    Да и в мануале про mysql_close() пишут, что закрывать соединение необязательно, ибо PHP сам закрывает все non-persistent соединения после завершения работы скрипта…

  23. Vadim Voituk

    Vladimir,
    Хотите верьте, хотите нет – но в данном случае manual врет.
    О чем не раз подтверждает ошибка “Too many connections” на более-или-менее нагруженных проектах.

  24. Vladimir

    Vadim, я не спорю, но разработчики утверждают, что это всё ложь и провокация (соединение может не закрыться только в том случае, если Апач пал смертью храбых).

    А не может быть, что нагруженные проекты используют persistent connection? Это самая распространенная ошибка до сих пор.

  25. Vadim Voituk

    Думаю с использованием persistent-connection проект просто не доживает до статуса “нагруженного” :)

    Когда-то на PHPConf, за кофе, обсуждалась проблема незакрытия коннекта к БД и уже тогда мнение большиства было едино:
    За незакрытый коннектшн – ложить руки на твердую поверхность и стучать по ним до полного выпрямления.

    Тогда я правда ещё не наступал на too many connections, но реакцию публики помню хорошо.

Leave a Reply