Показаны сообщения с ярлыком mysql_use_result. Показать все сообщения
Показаны сообщения с ярлыком mysql_use_result. Показать все сообщения

четверг, 30 декабря 2010 г.

Вызов mysql_use_result в Python-MySQL

Точно так же, как и Perl DBI, Python-MySQL по умолчанию использует API функцию mysql_store_result, что на больших объемах выборки (тестировалось с выборкой возвращающей 17 млн. записей) приводит к использованию невероятного количества памяти - и результатов работы скрипта можно не ждать - ибо система будет увлеченно занята копированием страниц из свопа - и в своп. Метод борьбы с этой бедой - тот же, что и в DBI - нужно использовать функцию mysql_use_result (плата за экономию ресурсов проста и жестока - пока result не закрыт, никакой другой запрос на этом подключении выполнить нельзя).
На практике делается это так. При использовании модуля MySQLdb вместо обычного вызова метода cursor() без аргументов пишем:
cu = db.cursor(MySQLdb.cursors.SSCursor)
Скажем несколько слов о поведении данного типа курсоров. Как легко догадаться, привычный вызов fetchall() делает курсор на серверной стороне абсолютно безсмысленным - так что нужно использовать fetchone(). Вот тут-то нас и поджидает небольшая засада - очевидный цикл:
for i in range(0, cu.rowcount):
как раз и не работает, ибо для SSCursor rowcount не определен. Для любителей чисто питонского цикла
for rw in cu:
сразу скажу, что данная конструкция, как ни странно, работает правильно. Так же можно организовать цикл способом аналогичным тому, который предлагается ниже для модуля _mysql.
Так вот, переходим к этому модулю. Выигрыш в производительности он дает выдающийся - на моей выборке быстрее раз в 5 (даже быстрее, чем аналогичный скрипт на Perl), так что ради таких выгод вполне можно потерпеть несколько зубодробительный синтаксис (тот, кто знаком с MySQL C API ничего страшного в этом синтаксисе не увидит).
Работаем оно так:
db.query("select * from sometable")
rs = db.use_result()
Все это еще не беда. Беда начинается дальше. rs.num_rows() по причинам, изложенным выше, не работает, а потому с циклом придется слегка повозиться. Python - это вам не C и не Perl - оператор присваивания внутри условия цикла не поддерживается, и потому нужно организовывать достаточно уродливый цикл (последний раз что-то подобное приходилось делать в языке 1С Бухгалтерии):

rw = rs.fetch_row()
while rw:
    # do something
    rw = rs.fetch_row()
На этом сюрпризы не заканчиваются. rw в этом примере - вовсе не tuple, состоящий из полей запроса, а tuple, состоящий tuples, состоящих из полей (к счастью, по умолчанию, там этот tuple строго один - для возврата нескольких строк за раз fetch_row нужно вызывать с аргументом maxrows - значение 0 означает "все"). Так что присваивание переменным значений полей будет выглядеть так:
fld1, fld2, fld3 = rw[0]
Еще нужно помнить, что если MySQLdb приводит типы данных MySQL к аналогичным типам Python, то в _mysql все данные - тупые строки (если я правильно помню, аналогичные функции C API ведут себя аналогично).

четверг, 25 декабря 2008 г.

Тюнинг DBD::mysql


Ниже описаны два параметра соединения с сервером БД MySQL с помощью модуля DBD::mysql, которые могут использоваться для повышения производительности.
  1. mysql_server_prepare - операторы сохраняются на стороне сервера.
  2. Активируется с помощью добавления выражения mysql_server_prepare=1 к DSN, напр.:


    $dbh = DBI->connect('DBI:mysql:db1:localhost;mysql_server_prepare=1', 'user', 'pass');


    По идее такой подход должен повышать производительность массовых запросов. Проверяем на заведомо слабой машине (сервер и клиент на одном и том же компьютере). Миллион insert выполняется быстрее на 11%, миллион update - на 19%, миллион delete - на 11%. Стоило оно того? Не знаю. Но вроде противопоказаний не обнаружено.


  3. mysql_use_result - результат запроса не сохраняется на клиенте перед использованием (умолчательное поведение называется mysql_store_result). Может быть применено:
    1. на уровне database handler: добавлением mysql_use_result=1 к DSN или оператором

      $dbh->{mysql_use_result}=1;

    2. или на уровне statement handler - в операторе:

      $dbh->prepare('select * from bigtable', {mysql_use_result => 1});

      или (после prepare и до execute) оператором:

      $sth->{mysql_use_result} = 1;

    Пользу от такого поведения можно почуствовать, если оператор возвращает очень много записей - порядка нескольких миллионов. При умолчательном mysql_store_result perl (по крайней мере, v 5.8.8 на FreeBSD 7.0) пожирает всю доступную память - и умирает, а при mysql_use_result - ничего, живет - и даже обходится не очень большим количеством памяти. К недостаткам этого метода работы с результатом man-страница DBD::mysql (3) относит

    tends to block other processes,

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