Замість того, щоб запитувати, що є стандартною практикою, оскільки це часто незрозуміло та суб’єктивно, ви можете спробувати звернутися до самого модуля за порадою. Взагалі, використовувати with
ключове слово, як запропонував інший користувач, є чудовою ідеєю, але в цих конкретних обставинах воно може дати вам не ту функціональність, яку ви очікуєте.
Починаючи з версії 1.2.5 модуля, MySQLdb.Connection
реалізує протокол менеджера контексту з таким кодом ( github ):
def __enter__(self):
if self.get_autocommit():
self.query("BEGIN")
return self.cursor()
def __exit__(self, exc, value, tb):
if exc:
self.rollback()
else:
self.commit()
Уже існує кілька запитань і відповідей with
, або ви можете прочитати Пояснення оператора Python "with" , але, по суті, відбувається те, що __enter__
виконується на початку with
блоку та __exit__
виконується після виходу з with
блоку. Ви можете використовувати необов’язковий синтаксис with EXPR as VAR
для прив’язки об’єкта, який повертається __enter__
до імені, якщо ви збираєтеся посилатися на цей об’єкт пізніше. Отже, враховуючи вищевказану реалізацію, ось простий спосіб запиту вашої бази даних:
connection = MySQLdb.connect(...)
with connection as cursor:
cursor.execute('select 1;')
result = cursor.fetchall()
print result
Питання зараз полягає в тому, які стани з'єднання та курсору після виходу з with
блоку? __exit__
Спосіб , показаний вище викликів тільки self.rollback()
або self.commit()
, і ні один з цих методів йти викликати close()
метод. Сам курсор не має __exit__
визначеного методу - і не мало би значення, якщо б він це робив, оскільки with
керує лише підключенням. Отже, і з’єднання, і курсор залишаються відкритими після виходу з with
блоку. Це легко підтвердити, додавши наступний код до наведеного вище прикладу:
try:
cursor.execute('select 1;')
print 'cursor is open;',
except MySQLdb.ProgrammingError:
print 'cursor is closed;',
if connection.open:
print 'connection is open'
else:
print 'connection is closed'
Ви повинні побачити вивід "курсор відкритий; з'єднання відкрито", надрукований на stdout.
Я вважаю, що вам потрібно закрити курсор перед встановленням з'єднання.
Чому? MySQL C API , який є основою для MySQLdb
, не реалізує який - небудь об'єкт курсора, як це мається на увазі в документації модуля: «MySQL не підтримує курсори, однак, курсори легко емулюються.» Дійсно, MySQLdb.cursors.BaseCursor
клас успадковує безпосередньо від object
курсорів і не накладає такого обмеження щодо коміту / відкоту. Розробник Oracle сказав :
cnx.commit () перед cur.close () для мене звучить найбільш логічно. Можливо, ви можете піти за правилом: "Закрийте курсор, якщо він вам більше не потрібен". Таким чином, commit () перед закриттям курсору. Зрештою, для Connector / Python це не має великої різниці, але для інших баз даних може.
Я сподіваюся, це настільки близько, наскільки ви збираєтесь дійти до "стандартної практики" з цього питання.
Чи є якась суттєва перевага у пошуку наборів транзакцій, для яких не потрібні проміжні коміти, щоб вам не потрібно було отримувати нові курсори для кожної транзакції?
Я дуже сумніваюся, і намагаючись це зробити, ви можете внести додаткову людську помилку. Краще прийняти рішення про конвенцію і дотримуватися її.
Чи багато накладних витрат на отримання нових курсорів, чи це просто не велика проблема?
Накладні витрати незначні і взагалі не торкаються сервера баз даних; це повністю в рамках реалізації MySQLdb. Ви можете подивитися BaseCursor.__init__
на github, якщо вам справді цікаво знати, що відбувається, коли ви створюєте новий курсор.
Повертаючись до раніше, коли ми обговорювали with
, можливо, тепер ви можете зрозуміти, чому MySQLdb.Connection
клас __enter__
і __exit__
методи дають вам абсолютно новий об'єкт курсора в кожному with
блоці і не турбуєтеся відстежувати його або закривати в кінці блоку. Він досить легкий і існує виключно для вашої зручності.
Якщо для вас насправді так важливо керувати об’єктом курсора мікро, ви можете використовувати contextlib.closing, щоб компенсувати той факт, що об’єкт курсору не має визначеного __exit__
методу. Щодо цього, ви також можете використовувати його, щоб змусити об'єкт підключення закритися після виходу з with
блоку. Це повинно вивести "my_curs закрито; my_conn закрито":
from contextlib import closing
import MySQLdb
with closing(MySQLdb.connect(...)) as my_conn:
with closing(my_conn.cursor()) as my_curs:
my_curs.execute('select 1;')
result = my_curs.fetchall()
try:
my_curs.execute('select 1;')
print 'my_curs is open;',
except MySQLdb.ProgrammingError:
print 'my_curs is closed;',
if my_conn.open:
print 'my_conn is open'
else:
print 'my_conn is closed'
Зверніть увагу, що with closing(arg_obj)
не буде викликати аргумент об'єкт __enter__
і __exit__
методи; він буде викликати лишеclose
метод об'єкта аргументу в кінці with
блоку. (Щоб побачити це в дії, просто визначте клас Foo
з __enter__
, __exit__
та close
методи, що містять прості print
висловлювання, і порівняйте, що відбувається, коли ви робите, with Foo(): pass
з тим, що відбувається, коли ви робите with closing(Foo()): pass
.) Це має два суттєві наслідки:
По-перше, якщо ввімкнено режим автокомісії, MySQLdb буде BEGIN
явною транзакцією на сервері, коли ви використовуєте with connection
та фіксуєте або відкочуєте транзакцію в кінці блоку. Це поведінка за замовчуванням MySQLdb, призначена для захисту вас від поведінки MySQL за замовчуванням від негайного фіксації будь-яких та всіх операторів DML. MySQLdb припускає, що коли ви використовуєте диспетчер контексту, вам потрібна транзакція, і використовує явний BEGIN
для обходу налаштування автокомісії на сервері. Якщо ви звикли використовувати with connection
, ви можете подумати, що автокомісія відключена, коли насправді її лише обходили. Якщо додати, ви можете отримати неприємний сюрпризclosing
до вашого коду і втратити цілісність транзакцій; ви не зможете повернути зміни, ви можете почати бачити помилки паралельності, і це може бути не відразу очевидно, чому.
По- друге, with closing(MySQLdb.connect(user, pass)) as VAR
пов'язує об'єкт підключення до VAR
, на відміну від with MySQLdb.connect(user, pass) as VAR
, який пов'язує новий об'єкт курсора до VAR
. В останньому випадку у вас не буде прямого доступу до об’єкта підключення! Натомість вам довелося б використовувати connection
атрибут курсору , який забезпечує проксі-доступ до вихідного з'єднання. Коли курсор закритий, його connection
атрибут встановлюється на None
. Це призводить до припиненого зв’язку, який буде триматися, доки не відбудеться одне з наступного:
- Усі посилання на курсор видаляються
- Курсор виходить за межі області дії
- Час очікування зв’язку закінчився
- З’єднання закривається вручну за допомогою засобів адміністрування сервера
Ви можете перевірити це, відстежуючи відкриті підключення (у Workbench або за допомогоюSHOW PROCESSLIST
), виконуючи наступні рядки по одному:
with MySQLdb.connect(...) as my_curs:
pass
my_curs.close()
my_curs.connection
my_curs.connection.close()
del my_curs