flask-sqlalchemy中 backref lazy的參數實例解釋和選擇

官方文檔:http://docs.sqlalchemy.org/en/rel_1_0/orm/basic_relationships.html#relationship-patternshtml

最近在學習到Flask中的Sqlalchemy, 不過在看到數據庫關係db.relations()時對lazy這個參數一直很模糊。主要是看到Flask Web開發這本書中對關注與被關注的關係建模中,被lazy的使用繞暈了。

看官方文檔,也得不到多少信息,因而就本身實踐,從lazy參數的不一樣值所執行的sql語句出發,結合one-to-manymany-to-many的關係,分析lazy參數取不一樣值(dynamic, joined, select)在不一樣場景下的選擇,由於涉及到數據庫性能問題,選擇不一樣差異很大,尤爲在數據量比較大時。
如下的實例均是基於以下的模型和表:主要側重對relationship中的backref的lazy屬性作修改。mysql

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 '<Student: %r>' %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 '<Class: %r>' %self.name

基本介紹

首先看官網的關於lazy的說明:sql

lazy 決定了 SQLAlchemy 何時從數據庫中加載數據:,有以下四個值:(其實還有個noload不經常使用)
select: (which is the default) means that SQLAlchemy will load the data as necessary in one go using a standard select statement.
joined: tells SQLAlchemy to load the relationship in the same query as the parent using a JOIN statement.
subquery: works like ‘joined’ but instead SQLAlchemy will use a subquery.
dynamic : is special and useful if you have many items. Instead of loading the items SQLAlchemy will return another query object which
you 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數據庫

通俗了說,select就是訪問到屬性的時候,就會所有加載該屬性的數據。joined則是在對關聯的兩個表進行join操做,從而獲取到全部相關的對象。dynamic則不同,在訪問屬性的時候,並無在內存中加載數據,而是返回一個query對象, 須要執行相應方法才能夠獲取對象,好比.all().下面結合實例解釋這幾個的使用場景。app

實例

首先是最開始一對多關係中,改動以下:將的lazy改成select:性能

students = db.relationship('Student', backref='_class', lazy="select")

這樣的話, class.students會直接返回結果列表:學習

>>> from app.models import Student as S, Class as C
>>> c1=C.query.first()
>>> c1.students
[<Student: u'test'>, <Student: u'test2'>, <Student: u'test3'>]

這種狀況下,在數據量較大或者想作進一步操做時候,不太方便,所以這個時候, dynamic就用上了:this

students = db.relationship('Student', backref='_class', lazy="dynamic")

一樣看看結果:spa

>>> from app.models import Student as S, Class as C
>>> s1=S.query.first()
>>> c1=C.query.first()
>>> c1.students
<sqlalchemy.orm.dynamic.AppenderBaseQuery object at 0x7f007d2e8ed0>
>>> 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()
[<Student: u'test'>, <Student: u'test2'>, <Student: u'test3'>]

能夠看到, 執行c1.student返回的是是一個 query對象,而且該對象的sql語句也能夠看到,就是簡單查詢了Student。而若是lazy=select 或者 joined均是直接返回結果。 須要注意的是, lazy="dynamic"只能夠用在一對多和多對對關係中,不能夠用在一對一和多對一中,若是返回結果只有一個的話,也就無須要延遲加載數據了。
前面說的都是給當前屬性加lazy屬性,backref的lazy默認都是select,若是給反向引用backref加lazy屬性呢? 直接使用backref=db.backref('students', lazy='dynamic' 便可。這個在多對多關係須要進行考量。
先看一個最基本的多對多關係:code

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 '<Student: %r>' %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 '<Class: %r>' %self.name

一樣執行結果能夠看到:

>>> s1=S.query.first()
>>> c1=C.query.first()
>>> s1._class
[<Class: u'class1'>, <Class: u'class2'>]
>>> c1.students
<sqlalchemy.orm.dynamic.AppenderBaseQuery object at 0x7ff8691a8610>
>>> c1.students.all()
[<Student: u'test'>, <Student: u'test2'>, <Student: u'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

能夠看到這個跟一對多關係中的很相似,只不過s1._class成爲了集合形式, 由於backref="_class"默認仍然是select,因此直接返回結果,而c1.students的sql語句也僅僅是查詢了students。可是若是修改反向引用的lazyjoined:

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()
[<Student: u'test'>, <Student: u'test2'>, <Student: u'test3'>]
>>> s1._class
[<Class: u'class1'>, <Class: u'class2'>]

首先不變的仍是s1._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實體也都查詢出來了。 可是貌似在這個例子中沒有意義,由於這種多對多的關係比較簡單,關聯表甚至都不是模型,只有兩個外鍵的id, 上述代碼中的registrations是直接被sqlalchemy接管的,程序沒法直接訪問的。
在下面的多對多例子中,咱們能夠看到上述的lazy方式的優點,咱們把關聯表改成實體model,而且額外增長一個時間信息。模型代碼以下:

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 '<Student: %r>' %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 '<Class: %r>' %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()
[<app.models.Registration object at 0x7f0348018ed0>, <app.models.Registration object at 0x7f0348018f50>]
>>> c1.students.all()
[<app.models.Registration object at 0x7f0348018ed0>, <app.models.Registration object at 0x7f03480412d0>, <app.models.Registration object at 0x7f034c32f250>]

能夠看到返回值是Registration兩個對象, 再也不直接返回StudentClass對象了。若是想要獲取的話,可使用給Registration加的反向引用:

>>> map(lambda x: x._class, s1._class.all())
[<Class: u'class1'>, <Class: u'class2'>]
>>> map(lambda x: x.student, c1.students.all())
[<Student: u'test'>, <Student: u'test2'>, <Student: u'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操做,獲取到了相應的StudentClass對象,換句話說,把Registration的student_class兩個回引屬性均指向了對應的對象, 也就是說,s1._class這一條查詢語句就能夠把上述操做都完成。這個就是backref=db.backref('_class', lazy='joined')的做用。 
下面再看看把lazy改成select的狀況:

###
_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)
[<Class: u'class1'>, <Class: u'class2'>]

十分簡單的sql語句,僅僅查詢返回了 Registration對象, 雖然結果同樣,可是每個Registration對象訪問_class屬性時,又各自都查詢了一遍數據庫! 這是很重的! 好比一個class有100個student, 那麼獲取class.students須要額外查詢100次數據庫! 每一次數據庫的查詢代價很大,所以這就是joined的做用了。

相關文章
相關標籤/搜索