#python #database #pyodbc
Вопрос:
У меня есть класс DB connector, основанный на aioodbc (оболочке pyodbc) python lib (суперкласс почти такой же, но для подключения к базе данных PostreSQL). Моя служба использует экземпляр этого класса для взаимодействия с базой данных MS SQL. Если служба часто взаимодействует с базой данных, все в порядке, но если она не взаимодействует в течение примерно 10 секунд (очень короткий период), у меня возникает ошибка: The cursor's connection has been closed.
Я использую там пул соединений.
У вас есть какие-либо идеи, как решить эту проблему? Со стороны базы данных все выглядит нормально.
class MSDBClient(DBClient):
def __init__(self, settings: Dict[str, Dict[str, Union[str, Dict]]], *,
loop: Optional[asyncio.AbstractEventLoop] = None,
pool_settings: Optional[Dict] = None):
super().__init__(settings, loop=loop, pool_settings=pool_settings)
async def setup(self):
async with self._lock:
for try_num in range(self.db_pool_tries):
try:
self.db_pool = await aioodbc.create_pool(loop=self._loop, **self._settings)
self.db_select_result = await self.select('select top 1 id from dbo.risk_rule_type;', [])
if self.db_select_result:
logger.info('MS SQL Connection Established {uid}@{server}:{port}/{database}'.format(
**self._login_data))
else:
raise DBNoDataError(f'Select resulted as {self.db_select_result}')
except Exception as e:
logger.error(f'Initial MS db pool error: {e}, sleep for {self.db_pool_timeout_seconds} second[s]')
await asyncio.sleep(self.db_pool_timeout_seconds)
finally:
if self.db_pool and self.db_select_result:
break
if try_num == self.db_pool_tries - 1:
raise DBLoginTimeoutError(f'All {self.db_pool_tries} tries exceeded')
async def close(self):
async with self._lock:
try:
self.db_pool.close()
await self.db_pool.wait_closed()
except Exception as e:
logger.error(f'MS db pool error: {e}')
async def select(self, query: str, values: List) -> List[asyncpg.Record]:
values = values or ()
return await self._select(query=query, values=values)
async def first(self, query: str, values: List) -> Optional[asyncpg.Record]:
values = values or ()
return await self._first(query=query, values=values)
async def _select(self, query: str, values: List):
try:
async with self.db_pool.acquire() as conn:
async with conn.cursor() as cur:
await cur.execute(query, *values)
return await cur.fetchall()
except Exception as e:
# TODO RAISE CUSTOM EXCEPTION
logger.error(f'{e}:{query}')
return {}
async def _first(self, query: str, values: List):
try:
async with self.db_pool.acquire() as conn:
async with conn.cursor() as cur:
await cur.execute(query, *values)
return await cur.fetchone()
except Exception as e:
# TODO RAISE CUSTOM EXCEPTION
logger.error(f'{e}:{query}')
return {}
async def _execute(self, query: str, values: List):
try:
async with self.db_pool.acquire() as conn:
async with conn.cursor() as cur:
await cur.execute(query, *values)
await conn.commit()
except pyodbc.Error as e:
logger.error(f'_execute pyodbc error: {e}')
except Exception as e:
logger.error(f'_execute error: {e}')
async def insert(self, query: str, values: List):
return await self._execute(query=query, values=values)
Ответ №1:
Я углубился в библиотеку aioodbc и нашел своего рода решение.
В моем случае соединение закрывается независимо примерно через несколько секунд (в свойствах соединения closed=True
).
Я не уверен, вызвано ли это проблемами с конфигурацией базы данных или библиотекой.
Решение
передайте pool_recycle
параметр, превышающий -1, в aioodbc.create_pool()
когда он передается, время использования соединения проверяется перед запросом, и если оно превышает переданное значение, соединение закрывается, а если количество подключений в пуле меньше минимального, новое соединение создается и используется для запроса базы данных
смотрите _fill_free_pool
в aioodbc/pool.py
Примечание. Соединение обновляется почти при каждом запросе, что занимает разумное количество времени