Flask-sqlalchemy中 backref lazy的引數例項解釋和選擇
官方文件:http://docs.sqlalchemy.org/en/rel_1_0/orm/basic_relationships.html#relationship-patterns
最近在學習到Flask中的Sqlalchemy, 不過在看到資料庫關係db.relations()
時對lazy
這個引數一直很模糊。主要是看到Flask Web開發這本書中對關注與被關注的關係建模中,被lazy的使用繞暈了。
看官方文件,也得不到多少資訊,於是就自己實踐,從lazy
引數的不同值所執行的sql語句出發,結合one-to-many
和many-to-many
的關係,分析lazy引數取不同值(dynamic, joined, select
)在不同場景下的選擇,因為涉及到資料庫效能問題,選擇不同差別很大,尤其在資料量比較大時。
以下的例項均是基於如下的模型和表:主要側重對relationship
中的backref的lazy
屬性做修改。
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
的說明:
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()
.下面結合例項解釋這幾個的使用場景。
例項
首先是最開始一對多關係中,改動如下:將一
的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
就用上了:
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
<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'
即可。這個在多對多關係需要進行考量。
先看一個最基本的多對多關係:
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
的作用了。
相關文章
- 核範數與規則項引數選擇
- css選擇器,帶例項CSS
- CSS 選擇器 - 帶例項CSS
- 可變引數例項
- Python中Scrapy框架元素選擇器XPath的簡單例項Python框架單例
- 用圖表和例項解釋 Await 和 AsyncAI
- 選項中選擇現在
- [Hive]建表例項與引數解釋——自定義表的儲存格式(textfile、sequencefile、refile)Hive
- jenkins 動態引數實現可供選擇的用例執行列表Jenkins
- 數學知識-核函式的通俗解釋例項函式
- mydumper和myloader使用引數解釋
- redis info memory命令的各項引數解釋彙總Redis
- Golang中命名引數的高階使用技巧與例項分析Golang
- C#out引數的簡單例項C#單例
- DG配置過程中的引數解釋
- 例項詳解構建數倉中的行列轉換
- 健壯的例項變數 (Non Fragile ivars)和脆弱的例項變數(Fragile ivars)變數
- java 執行緒池的初始化引數解釋和引數設定Java執行緒
- 選購交換機的引數依據和主要的引數指標詳解指標
- 關於C99可變引數巨集的例項程式碼講解
- 3個例項介紹shell指令碼中幾個特殊引數的用法指令碼
- select下拉選擇第一個選項為空白、option無法選中的解決辦法,
- C# 中 釋出訂閱例項C#
- python中類的建立和例項化Python
- TypeScript 函式可選引數和預設引數TypeScript函式
- jenkins 實現二級聯動選擇引數Jenkins
- /etc/sysctl.conf引數解釋
- expdp/impdp 詳細引數解釋
- 域名選擇注意事項
- 防抖和節流(例項講解)
- C#中的虛方法(virtual)例項講解C#
- Winform 工具欄 ToolStripMenuItem下拉選擇項選中對勾不居中ORMUI
- css中:not()選擇器和jQuery中.not()方法CSSjQuery
- 幾大排序總結(上)!圖解解析+程式碼例項(冒泡、選擇、插入、希爾、快排)排序圖解
- PHP弱型別引發的漏洞例項PHP型別
- win10引導選項在哪 win10設定引導選項的辦法Win10
- 類和例項
- redis持久化相關引數解釋Redis持久化