Pull to refresh

Comments 38

эх собиру минусы за упоминания того что нельзя упоминать, но все же

Чем ваш connection отличается от http://php.net/manual/ru/class.pdo.php (да знаю сейчас отличается, и текст запроса в курсоре, но тот вариант что по ссылкам кажется более продуман тем то что описано у вас)
ну и соотвественно cursor от http://php.net/manual/ru/class.pdostatement.php
мне кажется немного обсуждений и придете к тому же интерфейсу что я указал по ссылкам
А почему общие подходы решения стандартных задач обязательно должны отличаться в разных языках? Наоборот, чем меньше отличий, тем проще с этим работать.
Так если посмотреть, то и принцип работы с файлами, и принципы парсинга JSON или XML, те же регулярные выражения очень похоже реализованы в разных языках.
Проблема то здесь в чем?
1) cursor.execute(«SELECT * FROM 100MBtable»)
2) results = cursor.fetchall()
Интересует, что происходит «под капотом»
Правильно ли я понимаю, что первая команда создаст на сервере БД структуру размером 100МБ, а вторая — перекачает её по сети клиенту? Или это как-то иначе работает?
Если используется стандартный курсор, то да, причем:
* Объем данных будет значительно больше чем размер самой базы
* Даже если используется fetchone() все равно будет загружена вся информация целиком сразу
Для решения таких проблем существуют специальные классы курсоров (например SSCursor), которые позволяют хранить результат запроса на сервере.
Вот подобные вопросы на StackOverflow:
http://stackoverflow.com/questions/4559402/the-memory-problem-about-mysql-select
http://stackoverflow.com/questions/337479/how-to-get-a-row-by-row-mysql-resultset-in-python
cur.execute("SELECT * FROM Test WHERE testID > :tid", {'tid': 10})

получаю:
You have an error in your SQL syntax;...

пакеты MySQLdb и mysql.connector, оба ругаются на "?":
cur.execute("SELECT * FROM Test WHERE testID > ?", (10,))

Not all parameters were used in the SQL statement

Если не указать запятую при одном параметре (10,) параметр будет передан как есть, а не в кортеже. MySQLdb выругается вот так:
not all arguments converted during string formatting

а mysql.connector ругается на ошибку sql-синтаксиса (near ?)

python v2.7
mariadb 10.1

Судя по документации, именованные параметры в mysql должны задаваться через "@". Но в таком случае, хотя и нет исключений при вызове execute, результат пустой.

У себя в скриптах использую .format(), но их запускаю только я, поэтому sql-инъекции маловероятны.
Действительно, %s работает как позиционный аргумент. Спасибо за информацию!
Гениально! Спасибо тебе, добрый человек.
В MySQLdb paramstyle == 'format', по умолчанию. Выставил в 'pyformat', теперь работает так:
cur.execute("SELECT * FROM Test WHERE testID > %(tid)s", {'tid': 10})

Единственное, в данном модуле не получится использовать %d, например. П.ч. все аргументы прогоняются через db.literal(), который возвращает строку.
А как Вы смогли изменить этот параметр?
На форумах в нескольких местах находил информацию, что он информационный и «вшит» в сам модуль базы данных.

Я пробовал менять его для SQLite — значение параметра меняется, но работать с новым типом подстановок он не начинает:

import sqlite3

print(sqlite3.paramstyle)       # qmark
sqlite3.paramstyle = 'format'
print(sqlite3.paramstyle)       # format

conn = sqlite3.connect('Chinook_Sqlite.sqlite')
cursor = conn.cursor()

cursor.execute("SELECT Name FROM Artist ORDER BY Name LIMIT %s", ('2'))
# sqlite3.OperationalError: near "%": syntax error


Вот кстати полезная ссылка с изящным обходным решением проблемы: http://stackoverflow.com/questions/12184118/python-sqlite3-placeholder

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


и да, я не прав, про то, что это можно нормально настраивать. Там на самом деле обычный pyformat.
Автор вкатал тупой if в этом месте и подстановку в запрос без всякой защиты от инъекций https://github.com/farcepest/MySQLdb1/blob/master/MySQLdb/cursors.py#L183

Все параметры «прогоняются» через db.literal(), который, в свою очередь, вызывает escape() у _mysql.connection.
print("LIKE %s" % conn.escape("'; select 1"))

выводит
LIKE '\'; select 1'

Т.е. защита от инъекций все-таки есть.
Вот уж совпало, так совпало…
Я ловил ошибки, пытаясь применить именованные параметры по докам mysql (вида param). Бегло прочитал про paramstyle и установил его в pyformat. Попробовал %(param)s — сработало, обрадовался, хотя paramstyle в действительности не причем. Если я правильно понимаю, то этот атрибут модуля носит информативный характер, т.е. автор модуля этим атрибутом указывает способ форматирования параметров. Копание в исходниках показывает, что он нигде не используется и никуда не передается.
Для большей устойчивости программы (особенно при операциях записи) можно оборачивать инструкции обращения к БД в блоки «try-except-else» и использовать встроенный в sqlite3 «родной» объект ошибок, например, так:
try:
    cursor.execute(sql_statement)
    result = cursor.fetchall()
except sqlite3.DatabaseError as err:       
    print("Error: ", err)
else:
    conn.commit()
И снова ограничиваемся скучным и унылым Tuple.
За что любил PHP — при любой правке порядка полей в SQL запросе, если поле хотя бы есть, конструкция вида $row['field'] вполне успешно отдаёт его значение. В случае с простой реализацией запросов в питоне уже не всё так радужно, но можно сделать чуть посложнее.

Такую приятную вещь, как row_factory Вы забыли. Она позволяет брать метаданные из запроса и обращаться в итоге к результату, например по имени столбца.
По сути — callback для обработки данных при возврате строки. Да еще и полезнейший cursor.description, где есть всё необходимое.

Фрагмент из справки
= = =
import sqlite3

def dict_factory(cursor, row):
d = {}
for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx]
return d

con = sqlite3.connect(":memory:")
con.row_factory = dict_factory
cur = con.cursor()
cur.execute(«select 1 as a»)
print(cur.fetchone()[«a»])

а это уже разное от драйвера к драйверу, чем меня спецификация db-api2 изрядно расстраивает.
Очень не хватает четкой спеки на параметры, специфицированного autocommit в стандарте, serverside-курсоров,


dict или объекты во всех драйверах тоже реализованы, но по-разному и это печально.
Нет connection pooling и асинхронщины, что встаёт костью в горле, когда скрещиваешь это с каким-нибудь asyncio (я в своё время пытался скрещивать с twisted, примерно такого же порядка проблемы).
Пришло время описывать db api3 =)

UFO just landed and posted this here
Не знаю зачем вам такое, но это работает:
cursor.execute('''INSERT INTO t1(f) VALUES('v1'); INSERT INTO t2(f) VALUES('v2'); INSERT INTO t3(f) VALUES('v3');''', [])

python 3.6, psycopg 2.6
Лучше всё-таки не лепить три запроса в один скрипт, а: выполнить коннект, три раза отдельно выполнить execute с правильной интерполяцией значений, а потом выполнить commit.

А если будут траблы со вставкой множества значений в одну таблицу (там действительно ужасный оверхед, вставлял как-то котировки), то здесь поможет рецепт.
UFO just landed and posted this here
> conn.commit()

а почему все операции через курсор делаются, а коммит у коннекта вызывается?
Потому что может быть несколько курсоров на один коннект, и судя по всему нет возможности коммитить по отдельности
Если это так и нет осмысленного объяснения почему это так, то это баг в API
Я в питоне новичок, но как понимаю курсор тоже надо закрывать и правильно это делать как-то так
with closing(connection.cursor()) as cursor:
ну и с коннектом видимо также нужно поступать
UFO just landed and posted this here
Благодарю за дополнение, но вопрос с коммитами про другое был — почему коммит вызывается у коннекта, а не у курсора

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


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

Да даже если бы это ничего не ломало это был бы косяк в API, в питоне обычно всё логично устроено, может мы чего-то не знаем?

PEP-249 ничего по этому поводу не говорит, но я понимаю, почему авторы вытащили курсоры в отдельную сущность — их точно даже может существовать больше одного на соединение в некоторых БД.


MySQLdb и psycopg2 емнип делают close всем открытым курсорам этого соединения при commit.

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

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

Если транзакция общая на несколько курсоров, то было бы логичнее её явно начинать, а потом явно коммитить, а по дефолту транзакция на каждый курсор отдельная должна быть

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


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

А подскажите пожалуйста, причину вот этого:
>> # Обратите внимание, даже передавая одно значение — его нужно передавать кортежем!
Не совсем понимаю, почему обязательно делать список из кортежей, вместо обычных строк?
Это особенность реализации метода .executemany(), который работает с коллекцией коллекций, а не коллекцией единичных значений, поэтому даже для единичного значения его надо передавать как коллекцию, хотя не обязательно кортеж.

http://stackoverflow.com/questions/19154324/psycopg2-executemany-with-simple-list
http://stackoverflow.com/questions/5331894/i-cant-get-pythons-executemany-for-sqlite3-to-work-properly
Дополнения из комментариев от Igelko, paratagas, KurtRotzke, remzalp были добавлены в статью с указанием авторства. Большое спасибо за такие полезные дополнения!
Sign up to leave a comment.

Articles

Change theme settings