官方文檔: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-many
和many-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。可是若是修改反向引用的lazy
爲joined
:
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兩個對象, 再也不直接返回Student
和Class
對象了。若是想要獲取的話,可使用給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的_class
和student
時候, 還需不須要再查詢一遍數據庫呢?
下面經過查看執行的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
兩個回引屬性均指向了對應的對象, 也就是說,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
的做用了。