SQLAlchemy иногда ошибочно возвращает пустой результат

SQLAlchemy v1.0.6 
cx_Oracle v5.2

У нас некоторое время была проблема с нашим производственным кодом, и, наконец, мы сузили ее до данных, возвращаемых из SQLAlchemy.

Выполнение одного и того же запроса несколько раз иногда возвращает пустой результат. При некоторых условиях мы можем заставить его возвращать пустой результат каждый раз, когда код выполняется. И это несмотря на то, что данные в базе данных вообще не изменились и что версии одного и того же запроса на чистом SQL, запущенные непосредственно в cx_Oracle, всегда возвращают правильный результат.

Вот декларативный код для SQLAlchemy:

class Database:
    def __init__(self, service_name, database, username, password):
        """
        service_name (str): The service name as defined in tnsnames.ora.
        database (str): The database within the chosen service.
        """
        self.engine = create_engine(
            r'oracle+cx_oracle://{username}:{password}@{service_name}'.format(username=username, password=password,
                                                                              service_name=service_name),
            case_sensitive=False)
        self.session_maker = sessionmaker(bind=self.engine, autoflush=False, autocommit=False)

        # Database name must be injected into every table definition; this is why tables must be procedurally generated.
        self.Base = declarative_base()  # base class for all database tables
        self.build_tables(database)

    def make_session(self):
        """Create a read-only session for the database."""
        def readonly_abort():
            raise Exception('writing is prohibited; db is read-only')

        session = self.session_maker()
        session.flush = readonly_abort
        return session

    def build_tables(self, database):
        class Lot(self.Base):
            __tablename__ = 'lot'
            __table_args__ = {'schema': database}
            lot_key = Column(Integer, primary_key=True)
            lot_id = Column(String, name='lot_id')

        self.lot = Lot

И вот тестовый код:

def sqlalchemy_test():
    db = dp_orm.Database(service_name, database)
    session = db.make_session()
    cursor = session.query(db.lot)
    results = cursor.first()
    if results is None:
        raise Exception

def cx_oracle_test():
    import cx_Oracle
    import set_environment_variables
    conn = cx_Oracle.Connection(username, password, service_name)
    cursor = conn.cursor()

    c = cursor.execute('SELECT * FROM {}.lot WHERE rownum <= 1'.format(database))
    results = list(c)
    if len(results) != 1:
        raise Exception

Первая функция, sqlalchemy_test, будет ошибаться примерно в 50% случаев. Вторая функция, cx_oracle_test, еще не ошиблась. Теперь вот что интересно: проблема исчезает, если мы вводим паузу на несколько секунд между cursor = session.query(db.lot) и results = cursor.first(). Так что это похоже на какую-то проблему со временем.

Любая подсказка, что здесь происходит?

РЕДАКТИРОВАТЬ: я упростил код, необходимый для создания ошибки. Вот:

from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

# Fix environment variables
import os
try:
    del os.environ['ORACLE_HOME']
except KeyError:
    pass
os.environ['TNS_ADMIN'] = r'C:\product\11.1.0\client_1\network\admin'
os.environ['PATH'] = r'C:\product\11.1.0\client_1\BIN;' + os.environ['PATH']

engine = create_engine(r'oracle+cx_oracle://{username}:{password}@{service_name}'.format(username='USER', password='PASSWORD', service_name='SERVICE'))
session_maker = sessionmaker(bind=engine)
base_class = declarative_base()

class Lot(base_class):
    __tablename__ = 'lot'
    __table_args__ = {'schema': 'SCHEMA_NAME'}
    lot_key = Column(Integer, primary_key=True)
    lot_id = Column(String)

session = session_maker()
cursor = session.query(Lot)
result = cursor.first()
if result is None:
    raise Exception

person Vijchti    schedule 13.07.2015    source источник
comment
Несколько лет назад у меня была проблема с некоторыми драйверами mysql, которые вызывали странные, казалось бы, недетерминированные проблемы, подобные этой. Я не могу сказать об Oracle, но вы можете попробовать другой драйвер базы данных, чтобы увидеть, поможет ли это?   -  person Ned Rockson    schedule 14.07.2015
comment
Спасибо за предложение, Нед!   -  person Vijchti    schedule 14.07.2015
comment
Дальнейшее тестирование показывает, что это происходит только в SQLAlchemy. Ни cx_Oracle сам по себе, ни даже при копировании SQL, сгенерированного SQLAlchemy, ни в другой системе, которая использует JDBC для подключения к базе данных — ничто не создает эту проблему, кроме SQLAlchemy. Самое странное...   -  person Vijchti    schedule 14.07.2015
comment
Дополнительные тесты: если запрос ошибочно возвращает None, то все последующие запросы будут возвращать None. Однако если запрос выполняется через session.execute(), он всегда будет работать. Мой обходной путь на данный момент: явно проверить, что SQLAlchemy работает, прежде чем выполнять какие-либо реальные запросы, а затем повторно подключиться, если какие-либо тесты возвращают None.   -  person Vijchti    schedule 14.07.2015
comment
Похоже, повторное подключение к базе данных ничего не дает. Вся программа должна быть перезапущена, чтобы получить другой результат.   -  person Vijchti    schedule 14.07.2015
comment
Дополнительное исследование: эта ошибка возникает только при использовании cursor.first() или любого фрагмента курсора. Если я использую cursor.all(), ошибка никогда не проявляется.   -  person Vijchti    schedule 15.07.2015
comment
... но даже если all() работает, попытка запроса на основе отношения все равно вернет None.   -  person Vijchti    schedule 15.07.2015
comment
Является ли столбец lot_key в вашей таблице lot действительно первичным ключом (что означает, что для этого столбца таблицы в базе данных определено ограничение первичного ключа) или это просто определение SQLAlchemy? У меня были проблемы, очень похожие на ваши, когда в таблице не было реального PK, а значения столбца, отмеченного в SQLAlchemy как PK, не были уникальными.   -  person Radosław Roszkowiak    schedule 23.07.2015
comment
Это обсуждение было продолжено на:bitbucket.org/zzzeek/sqlalchemy. /проблемы/3491/   -  person Vijchti    schedule 24.07.2015


Ответы (1)


Ответ найден: проблема была в cx_Oracle 5.2. Переустановка cx_Oracle 5.1.3 решила проблему.

person Vijchti    schedule 24.07.2015