Как SQLAlchemy обрабатывает уникальное ограничение в определении таблицы

#sqlalchemy #unique

#sqlalchemy #уникальный

Вопрос:

У меня есть таблица со следующим декларативным определением:

 class Type(Base):
    __tablename__ = 'Type'
    id = Column(Integer, primary_key=True)
    name = Column(String, unique = True)
    def __init__(self, name):
        self.name = name
  

Столбец «name» имеет уникальное ограничение, но я могу сделать

 type1 = Type('name1')
session.add(type1)
type2 = Type(type1.name)
session.add(type2)
  

Итак, как можно видеть, ограничение уникальности вообще не проверяется, поскольку я добавил в сеанс 2 объекта с одинаковым именем.

Когда я это делаю session.commit() , я получаю ошибку mysql, поскольку ограничение также находится в таблице mysql.

Возможно ли, что sqlalchemy заранее сообщает мне, что я не могу его создать, или идентифицирует его и не вставляет 2 записи с одинаковым столбцом «name»? Если нет, должен ли я сохранить в памяти все существующие имена, чтобы я мог проверить, существуют они или нет, перед созданием объекта?

Ответ №1:

SQLAlechemy не обрабатывает уникальность, потому что это невозможно сделать хорошим способом. Даже если вы отслеживаете созданные объекты и / или проверяете, существует ли объект с таким именем, существует условие гонки: любой другой процесс может вставить новый объект с именем, которое вы только что проверили. Единственное решение — заблокировать всю таблицу перед проверкой и снять блокировку после вставки (некоторые базы данных поддерживают такую блокировку).

Комментарии:

1. Я буду единственным, кто вставляет туда данные, поэтому я позабочусь о том, чтобы этого не произошло. Мой вопрос конкретно о том, как sqlalchemy обрабатывает уникальность поля

2. @duduklein Он не обрабатывает уникальность. И мой ответ описывает, почему.

Ответ №2:

AFAIK, sqlalchemy не обрабатывает ограничения уникальности в поведении python. Эти объявления «unique = True» используются только для наложения ограничений таблицы на уровне базы данных, и только тогда, если вы создаете таблицу с помощью команды sqlalchemy, т.Е.

 Type.__table__.create(engine)
  

или что-то подобное. Если вы создадите модель SA для существующей таблицы, в которой на самом деле нет этого ограничения, это будет выглядеть так, как если бы оно не существовало.

В зависимости от вашего конкретного варианта использования вам, вероятно, придется использовать шаблон типа

 try:
  existing = session.query(Type).filter_by(name='name1').one()
  # do something with existing
except:
  newobj = Type('name1')
  session.add(newobj)
  

или вариант, или вам просто нужно перехватить исключение mysql и восстановиться оттуда.

Ответ №3:

Из документов

 class MyClass(Base):
    __tablename__ = 'sometable'
    __table_args__ = (
            ForeignKeyConstraint(['id'], ['remote_table.id']),
            UniqueConstraint('foo'),
            {'autoload':True}
            )
  

Комментарии:

1. В чем разница между этим и использованием unique=True , представленным в исходном вопросе?

2. unique=True не предоставляет возможности указать уникальное имя для индекса, что вызовет проблемы с alembic. Смотрите: alembic.sqlalchemy.org/en/latest/naming.html

3. Хорошо, но отвечает ли это на первоначальный вопрос? Я думаю, что могло бы быть полезно еще несколько объяснений вашего отношения к вопросу OP.

4. Не уверен, но этому вопросу уже почти десять лет, и это не принятый ответ.

5. это не значит, что его все еще не часто обнаруживают. Я просто пытаюсь понять ваш ответ и объяснить мой -1.

Ответ №4:

.one() выдает два вида исключений: sqlalchemy.orm.exc.NoResultFound и sqlalchemy.orm.exc.MultipleResultsFound

Вы должны создать этот объект при возникновении первого исключения, если произойдет второе, вы все равно облажаетесь и не должны усугублять ситуацию.

 try:
  existing = session.query(Type).filter_by(name='name1').one()
# do something with existing
except NoResultFound:
  newobj = Type('name1')
  session.add(newobj)