Python aioodbc pyodbc.Ошибка программирования: соединение курсора было закрыто

#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

Примечание. Соединение обновляется почти при каждом запросе, что занимает разумное количество времени