Замість того, щоб запитувати, що є стандартною практикою, оскільки це часто незрозуміло та суб’єктивно, ви можете спробувати звернутися до самого модуля за порадою. Взагалі, використовувати 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