flask-sqlalchemyにおけるbackref lazyのパラメータインスタンスの解釈と選択

38187 ワード

公式ドキュメント:http://docs.sqlalchemy.org/en/rel_1_0/orm/basic_relationships.html#relationship-patterns
最近FlaskのSqlalchemyについて勉強していますが、db.relations()というパラメータはデータベース関係を見てもぼやけています.主にlazyという本の中で注目と注目の関係をモデリングしているのを見て、lazyの使用にめまいがしました.公式文書を見ても、あまり情報が得られないので、自分で実践し、Flask Web パラメータの異なる値から実行されるlazy文から、sqlone-to-manyの関係を結びつけて、lazyパラメータを分析して異なる値(many-to-many)の異なるシーンでの選択を取り、データベースの性能の問題にかかわるため、選択の違いが大きい.特にデータ量が大きい場合.以下の例は、dynamic, joined, selectのbackrefのrelationship属性を主に変更するモデルおよび表に基づいている.
registrations = db.Table('registrations',
                         db.Column('student_id', db.Integer, db.ForeignKey('students.id')),
                         db.Column('class_id', db.Integer, db.ForeignKey('classes.id')))
class Student(db.Model):
    __tablename__ = 'students'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64))
    class_id = db.Column(db.Integer, db.ForeignKey('classes.id'))
    def __repr__(self):
        return '' %self.name
class Class(db.Model):
    __tablename__ = 'classes'
    id = db.Column(db.Integer, primary_key=True)
    students = db.relationship('Student', backref='_class', lazy="dynamic")
    name = db.Column(db.String(64))
    def __repr__(self):
        return '' %self.name


基本的な紹介
まず公式サイトのlazyについての説明を見ます.
LazyはSQLAlchemyがいつデータベースからデータをロードするかを決定した:(実際にはnoloadがあまり使われていない)lazy:(which is the default)means that SQLAlchemy will load the data as necessary in one go using a standard select statementの4つの値がある.select : tells SQLAlchemy to load the relationship in the same query as the parent using a JOIN statement. joined : works like ‘joined’ but instead SQLAlchemy will use a subquery. subquery  : is special and useful if you have many items. Instead of loading the items SQLAlchemy will return another query object whichyou can further refine before loading the items. This is usually what you want if you expect more than a handful of items for this relationship
一般的には、dynamicは属性にアクセスすると、その属性のデータがすべてロードされます.selectは、関連する2つのテーブルに対してjoined操作を行い、関連するすべてのオブジェクトを取得する.joinは、属性にアクセスする際にメモリにデータをロードするのではなく、dynamicのオブジェクトを返し、queryのようなオブジェクトを取得するには、対応する方法を実行する必要がある.これらの使用シーンを例に合わせて説明します.
≪インスタンス|Instance|emdw≫
まず最初の一対の多関係では,.all()のlazyをselectに変更する.
students = db.relationship('Student', backref='_class', lazy="select")

これでclassstudentsは結果リストを直接返します.
>>> from app.models import Student as S, Class as C
>>> c1=C.query.first()
>>> c1.students
['test'>, 'test2'>, 'test3'>]

この場合、データ量が大きい場合や、さらに操作したい場合は、不便なので、この場合、 が使用されます.
students = db.relationship('Student', backref='_class', lazy="dynamic")

結果を見てみましょう.
>>> from app.models import Student as S, Class as C
>>> s1=S.query.first()
>>> c1=C.query.first()
>>> c1.students

>>> print c1.students
SELECT students.id AS students_id, students.name AS students_name
FROM students, registrations
WHERE :param_1 = registrations.class_id AND students.id = registrations.student_id
>>> c1.students.all()
['test'>, 'test2'>, 'test3'>]


実行dynamicは、c1.studentのオブジェクトを返し、そのオブジェクトのquery文は、sqlの単純なクエリであることも分かる.Studentがいずれも直接結果を返す場合.なお、lazy=select joinedは1対多と多対の関係でのみ使用でき、1対1と多対1では使用できません.戻り結果が1つしかない場合は、データのロードを遅らせる必要はありません.前に述べたのはすべて現在の属性にlazy="dynamic"の属性を加えて、backrefのlazyのデフォルトはすべてlazyで、もし逆参照selectにlazyの属性を加えたら?backrefをそのまま使えばいいです.これは多対多関係で考慮する必要がある.まず、最も基本的な多対多関係を見てみましょう.
registrations = db.Table('registrations',
                         db.Column('student_id', db.Integer, db.ForeignKey('students.id')),
                         db.Column('class_id', db.Integer, db.ForeignKey('classes.id')))
class Student(db.Model):
    __tablename__ = 'students'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64))
    # class_id = db.Column(db.Integer, db.ForeignKey('classes.id'))       ,      
    def __repr__(self):
        return '' %self.name
class Class(db.Model):
    __tablename__ = 'classes'
    id = db.Column(db.Integer, primary_key=True)
    students = db.relationship('Student', secondary=registrations, backref='_class', lazy="dynamic") #       
    name = db.Column(db.String(64))
    def __repr__(self):
        return '' %self.name


同じ実行結果が表示されます.
>>> s1=S.query.first()
>>> c1=C.query.first()
>>> s1._class
['class1'>, 'class2'>]
>>> c1.students

>>> c1.students.all()
['test'>, 'test2'>, 'test3'>]
>>> print c1.students
SELECT students.id AS students_id, students.name AS students_name
FROM students, registrations
WHERE :param_1 = registrations.class_id AND students.id = registrations.student_id


これは一対の多関係とよく似ているが,backref=db.backref('students', lazy='dynamic'が集合形式となっているだけで,s1._classのデフォルトはbackref="_class"であるため,直接結果を返し,selectのsql文もstudentsをクエリしただけであることがわかる.ただし、逆参照を変更したc1.studentslazyである場合、
students = db.relationship('Student', secondary=registrations,
                                           backref=db.backref('_class', lazy="joined"), lazy="dynamic")

結果を見てみましょう
....
>>> print c1.students
SELECT students.id AS students_id, students.name AS students_name, classes_1.id AS classes_1_id, classes_1.name AS classes_1_name
FROM registrations, students LEFT OUTER JOIN (registrations AS registrations_1 JOIN classes AS classes_1 ON classes_1.id = registrations_1.class_id) ON students.id = registrations_1.student_id
WHERE :param_1 = registrations.class_id AND students.id = registrations.student_id
>>> c1.students.all()
['test'>, 'test2'>, 'test3'>]
>>> s1._class
['class1'>, 'class2'>]


まず、joineds1._classか、直接データを返します.変更されたのはc1.studentsのsql文で、Studentオブジェクトをクエリーするだけでなく、関連テーブルとjoin操作を行うことで、関連するClassもクエリーしました.関連する意味は何ですか?sql文を直接実行した結果を見てみましょう.
mysql> SELECT students.id AS students_id, students.name AS students_name, classes_1.id AS classes_1_id, classes_1.name AS classes_1_name  FROM registrations, students LEFT OUTER JOIN (registrations AS registrations_1 JOIN classes AS classes_1 ON classes_1.id = registrations_1.class_id) ON students.id = registrations_1.student_id  WHERE 1 = registrations.class_id AND students.id = registrations.student_id;
+-------------+---------------+--------------+----------------+
| students_id | students_name | classes_1_id | classes_1_name |
+-------------+---------------+--------------+----------------+
|           1 | test          |            1 | class1         |
|           1 | test          |            2 | class2         |
|           2 | test2         |            1 | class1         |
|           3 | test3         |            1 | class1         |
+-------------+---------------+--------------+----------------+
4 rows in set (0.00 sec)


すなわち,クエリで得られたstudentsの対応するclassエンティティもすべてクエリされた.しかし、この例では意味がないようです.このような多対多の関係は比較的簡単で、関連テーブルはモデルではなく、2つの外部キーのidしかなく、上記のコードのregistrationssqlalchemyに直接引き継がれており、プログラムは直接アクセスできません.次の多対多の例では、上記のlazy方式の利点を見ることができ、関連テーブルをエンティティモデルに変更し、時間情報を追加します.モデルコードは次のとおりです.
class Registration(db.Model):
    '''   '''
    __tablename__ = 'registrations'
    student_id = db.Column(db.Integer, db.ForeignKey('students.id'), primary_key=True)
    class_id = db.Column(db.Integer, db.ForeignKey('classes.id'), primary_key=True)
    create_at = db.Column(db.DateTime, default=datetime.utcnow)
class Student(db.Model):
    __tablename__ = 'students'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64))
    _class = db.relationship('Registration', foreign_keys=[Registration.student_id],
                             backref=db.backref('student', lazy="joined"), lazy="dynamic")
    def __repr__(self):
        return '' %self.name
class Class(db.Model):
    __tablename__ = 'classes'
    id = db.Column(db.Integer, primary_key=True)
    students = db.relationship('Registration', foreign_keys=[Registration.class_id],
                               backref=db.backref('_class', lazy="joined"), lazy="dynamic")
    name = db.Column(db.String(64))
    def __repr__(self):
        return '' %self.name


データの事前準備:
mysql> select * from classes;
+----+--------+
| id | name   |
+----+--------+
|  1 | class1 |
|  2 | class2 |
+----+--------+
2 rows in set (0.00 sec)
mysql> select * from students;
+----+-------+
| id | name  |
+----+-------+
|  1 | test  |
|  2 | test2 |
|  3 | test3 |
+----+-------+
3 rows in set (0.00 sec)
mysql> select * from registrations;
+------------+----------+-----------+
| student_id | class_id | create_at |
+------------+----------+-----------+
|          1 |        1 | NULL      |
|          2 |        1 | NULL      |
|          3 |        1 | NULL      |
|          1 |        2 | NULL      |
+------------+----------+-----------+
4 rows in set (0.00 sec)


結果を見てみましょう.
>>> s1._class.all()
[, ]
>>> c1.students.all()
[, , ]

戻り値はRegistrationの2つのオブジェクトであり、StudentClassのオブジェクトは直接戻りません.取得するには、Registrationに追加された逆参照を使用します.
>>> map(lambda x: x._class, s1._class.all())
['class1'>, 'class2'>]
>>> map(lambda x: x.student, c1.students.all())
['test'>, 'test2'>, 'test3'>]

では問題が来ました.ここでRegistrationの_classstudentを呼び出すとき、データベースをもう一度調べる必要はありませんか?
次に、実行されたsql文を表示します.
>>> print s1._class
SELECT registrations.student_id AS registrations_student_id, registrations.class_id AS registrations_class_id, registrations.create_at AS registrations_create_at, classes_1.id AS classes_1_id, classes_1.name AS classes_1_name, students_1.id AS students_1_id, students_1.name AS students_1_name
FROM registrations LEFT OUTER JOIN classes AS classes_1 ON classes_1.id = registrations.class_id LEFT OUTER JOIN students AS students_1 ON students_1.id = registrations.student_id
WHERE :param_1 = registrations.student_id


前述の例と同様に、s1._classは、対応するclass情報を検索するだけでなく、joinの動作によって、対応するStudentおよびClassのオブジェクトを取得することができる.すなわち、Registrationのstudentおよび_classの2つのインデックス属性は、対応するオブジェクト、すなわち、s1._classというクエリー文は、上記の操作をすべて完了することができます.これがbackref=db.backref('_class', lazy='joined')の役割です.lazyselectに変更した場合を見てみましょう.
###
_class = db.relationship('Registration', foreign_keys=[Registration.student_id],
                         backref=db.backref('student', lazy="select"), lazy="dynamic")
###
students = db.relationship('Registration', foreign_keys=[Registration.class_id],
                           backref=db.backref('_class', lazy="select"), lazy="dynamic")

クエリ文を見てみましょう.
>>> s1=S.query.first()
>>> print s1._class
SELECT registrations.student_id AS registrations_student_id, registrations.class_id AS registrations_class_id, registrations.create_at AS registrations_create_at
FROM registrations
WHERE :param_1 = registrations.student_id
>>> map(lambda x : x._class , s1._class)
['class1'>, 'class2'>]


非常に簡単なsql文で、クエリだけでRegistrationオブジェクトが返され、結果は同じですが、Registrationオブジェクトごとに_classプロパティにアクセスすると、それぞれデータベースがクエリされます.これは重いですね.たとえばclassに100個のstudentがある場合、class.studentsを取得するには100回のデータベースを追加する必要があります.データベースのクエリーのたびにコストがかかるため、joinedの役割を果たします.
転載:https://www.cnblogs.com/lpxblog/p/7485469.html