SQLAlchemy 使用經驗
點擊上方「
Python開發
」,選擇「置頂公眾號」
關鍵時刻,第一時間送達!
最近在用 Python 做一個網站。除了 Tornado ,主要還用到了 SQLAlchemy。這篇就是介紹我在使用 SQLAlchemy 的過程中,學到的一些知識。首先說下,由於最新的 0.8 版還是開發版本,因此我使用的是 0.79 版,API 也許會有些不同。
因為我是搭配 MySQL InnoDB 使用,所以使用其他資料庫的也不能完全照搬本文。
接著就從安裝開始介紹吧,以 Debian/Ubuntu 為例(請確保有管理員許可權):
MySQL
apt-get install mysql-server
apt-get install mysql-client
apt-get install libmysqlclient15-dev
python-mysqldb
apt-get install python-mysqldb
easy_install
wget http://peak.telecommunity.com/dist/ez_setup.py
python ez_setup.py
MySQL-Python
easy_install MySQL-Python
SQLAlchemy
easy_install SQLAlchemy
如果是用其他操作系統,遇到問題就 Google 一下吧。我是在 Mac OS X 上開發的,途中也遇到些問題,不過當時沒記下來……
值得一提的是我用了 MySQL-Python 來連 MySQL,因為不支持非同步調用,所以和 Tornado 不是很搭。不過性能其實很好,因此以後再去研究下其他方案吧……
裝好後就可以開始使用了:
from sqlalchemy import create_engine
from
sqlalchemy
.
orm import sessionmaker
DB_CONNECT_STRING
=
"mysql+mysqldb://root:123@localhost/ooxx?"
engine
=
create_engine
(
DB_CONNECT_STRING
,
echo
=
True
)
DB_Session
=
sessionmaker
(
bind
=
engine
)
session
=
DB_Session
()
這裡的 DB_CONNECT_STRING 就是連接資料庫的路徑。「mysql+mysqldb」指定了使用 MySQL-Python 來連接,「root」和「123」分別是用戶名和密碼,「localhost」是資料庫的域名,「ooxx」是使用的資料庫名(可省略),「charset」指定了連接時使用的字符集(可省略)。
create_engine() 會返回一個資料庫引擎,echo 參數為 True 時,會顯示每條執行的 SQL 語句,生產環境下可關閉。
sessionmaker() 會生成一個資料庫會話類。這個類的實例可以當成一個資料庫連接,它同時還記錄了一些查詢的數據,並決定什麼時候執行 SQL 語句。由於 SQLAlchemy 自己維護了一個資料庫連接池(默認 5 個連接),因此初始化一個會話的開銷並不大。對 Tornado 而言,可以在 BaseHandler 的 initialize() 里初始化:
class
BaseHandler
(
tornado
.
web
.
RequestHandler
)
:
def initialize
(
self
)
:
self
.
session
=
models
.
DB_Session
()
def on_finish
(
self
)
:
self
.
session
.
close
()
對其他 Web 伺服器來說,可以使用 sqlalchemy.orm.scoped_session,它能保證每個線程獲得的 session 對象都是唯一的。不過 Tornado 本身就是單線程的,如果使用了非同步方式,就可能會出現問題,因此我並沒使用它。
拿到 session 後,就可以執行 SQL 了:
session
.
execute
(
"create database abc"
)
session
.
execute
(
"show databases"
).
fetchall
()
session
.
execute
(
"use abc"
)
# 建 user 表的過程略
session
.
execute
(
"select * from user where id = 1"
).
first
()
session
.
execute
(
"select * from user where id = :id"
,
{
"id"
:
1
}).
first
()
不過這和直接使用 MySQL-Python 沒啥區別,所以就不介紹了;我還是喜歡 ORM 的方式,這也是我採用 SQLAlchemy 的唯一原因。
於是來定義一個表:
from sqlalchemy import Column
from
sqlalchemy
.
types import
CHAR
,
Integer
,
String
from
sqlalchemy
.
ext
.
declarative import declarative_base
BaseModel
=
declarative_base
()
def init_db
()
:
BaseModel
.
metadata
.
create_all
(
engine
)
def drop_db
()
:
BaseModel
.
metadata
.
drop_all
(
engine
)
class
User
(
BaseModel
)
:
__tablename__
=
"user"
id
=
Column
(
Integer
,
primary_key
=
True
)
name
=
Column
(
CHAR
(
30
))
# or Column(String(30))
init_db
()
declarative_base() 創建了一個 BaseModel 類,這個類的子類可以自動與一個表關聯。
以 User 類為例,它的 __tablename__ 屬性就是資料庫中該表的名稱,它有 id 和 name 這兩個欄位,分別為整型和 30 個定長字元。Column 還有一些其他的參數,我就不解釋了。
最後,BaseModel.metadata.create_all(engine) 會找到 BaseModel 的所有子類,並在資料庫中建立這些表;drop_all() 則是刪除這些表。
接著就開始使用這個表吧:
from sqlalchemy import
func
,
or_
,
not_
user
=
User
(
name
=
"a"
)
session
.
add
(
user
)
user
=
User
(
name
=
"b"
)
session
.
add
(
user
)
user
=
User
(
name
=
"a"
)
session
.
add
(
user
)
user
=
User
()
session
.
add
(
user
)
session
.
commit
()
query
=
session
.
query
(
User
)
query
# 顯示SQL 語句
query
.
statement
# 同上
for
user
in
query
:
# 遍歷時查詢
user
.
name
query
.
all
()
# 返回的是一個類似列表的對象
query
.
first
().
name
# 記錄不存在時,first() 會返回 None
# print query.one().name # 不存在,或有多行記錄時會拋出異常
query
.
filter
(
User
.
id
==
2
).
first
().
name
query
.
get
(
2
).
name
# 以主鍵獲取,等效於上句
query
.
filter
(
"id = 2"
).
first
().
name
# 支持字元串
query2
=
session
.
query
(
User
.
name
)
query2
.
all
()
# 每行是個元組
query2
.
limit
(
1
).
all
()
# 最多返回 1 條記錄
query2
.
offset
(
1
).
all
()
# 從第 2 條記錄開始返回
query2
.
order_by
(
User
.
name
).
all
()
query2
.
order_by
(
"name"
).
all
()
query2
.
order_by
(
User
.
name
.
desc
()).
all
()
query2
.
order_by
(
"name desc"
).
all
()
session
.
query
(
User
.
id
).
order_by
(
User
.
name
.
desc
(),
User
.
id
).
all
()
query2
.
filter
(
User
.
id
==
1
).
scalar
()
# 如果有記錄,返回第一條記錄的第一個元素
session
.
query
(
"id"
).
select_from
(
User
).
filter
(
"id = 1"
).
scalar
()
query2
.
filter
(
User
.
id
>
1
,
User
.
name
!=
"a"
).
scalar
()
# and
query3
=
query2
.
filter
(
User
.
id
>
1
)
# 多次拼接的 filter 也是 and
query3
=
query3
.
filter
(
User
.
name
!=
"a"
)
query3
.
scalar
()
query2
.
filter
(
or_
(
User
.
id
==
1
,
User
.
id
==
2
)).
all
()
# or
query2
.
filter
(
User
.
id
.
in_
((
1
,
2
))).
all
()
# in
query4
=
session
.
query
(
User
.
id
)
query4
.
filter
(
User
.
name
==
None
).
scalar
()
query4
.
filter
(
"name is null"
).
scalar
()
query4
.
filter
(
not_
(
User
.
name
==
None
)).
all
()
# not
query4
.
filter
(
User
.
name
!=
None
).
all
()
query4
.
count
()
session
.
query
(
func
.
count
(
"*"
)).
select_from
(
User
).
scalar
()
session
.
query
(
func
.
count
(
"1"
)).
select_from
(
User
).
scalar
()
session
.
query
(
func
.
count
(
User
.
id
)).
scalar
()
session
.
query
(
func
.
count
(
"*"
)).
filter
(
User
.
id
>
0
).
scalar
()
# filter() 中包含 User,因此不需要指定表
session
.
query
(
func
.
count
(
"*"
)).
filter
(
User
.
name
==
"a"
).
limit
(
1
).
scalar
()
==
1
# 可以用 limit() 限制 count() 的返回數
session
.
query
(
func
.
sum
(
User
.
id
)).
scalar
()
session
.
query
(
func
.
now
()).
scalar
()
# func 後可以跟任意函數名,只要該資料庫支持
session
.
query
(
func
.
current_timestamp
()).
scalar
()
session
.
query
(
func
.
md5
(
User
.
name
)).
filter
(
User
.
id
==
1
).
scalar
()
query
.
filter
(
User
.
id
==
1
).
update
({
User
.
name
:
"c"
})
user
=
query
.
get
(
1
)
user
.
name
user
.
name
=
"d"
session
.
flush
()
# 寫資料庫,但並不提交
query
.
get
(
1
).
name
session
.
delete
(
user
)
session
.
flush
()
query
.
get
(
1
)
session
.
rollback
()
query
.
get
(
1
).
name
query
.
filter
(
User
.
id
==
1
).
delete
()
session
.
commit
()
query
.
get
(
1
)
增刪改查都涉及到了,自己看看輸出的 SQL 語句就知道了,於是基礎知識就介紹到此了。
下面開始介紹一些進階的知識。
如何批量插入大批數據?
可以使用非 ORM 的方式:
session
.
execute
(
User
.
__table__
.
insert
(),
[{
"name"
:
`
randint
(
1
,
100
)`,
"age"
:
randint
(
1
,
100
)}
for
i
in
xrange
(
10000
)]
)
session
.
commit
()
上面我批量插入了 10000 條記錄,半秒內就執行完了;而 ORM 方式會花掉很長時間。
如何讓執行的 SQL 語句增加前綴?
使用 query 對象的 prefix_with() 方法:
session
.
query
(
User
.
name
).
prefix_with
(
"HIGH_PRIORITY"
).
all
()
session
.
execute
(
User
.
__table__
.
insert
().
prefix_with
(
"IGNORE"
),
{
"id"
:
1
,
"name"
:
"1"
})
如何替換一個已有主鍵的記錄?
使用 session.merge() 方法替代 session.add(),其實就是 SELECT + UPDATE:
user
=
User
(
id
=
1
,
name
=
"ooxx"
)
session
.
merge
(
user
)
session
.
commit
()
或者使用 MySQL 的 INSERT … ON DUPLICATE KEY UPDATE,需要用到 @compiles 裝飾器,有點難懂,自己看吧:《SQLAlchemy ON DUPLICATE KEY UPDATE》 和 sqlalchemy_mysql_ext。
如何使用無符號整數?
可以使用 MySQL 的方言:
from
sqlalchemy
.
dialects
.
mysql import
INTEGER
id
=
Column
(
INTEGER
(
unsigned
=
True
),
primary_key
=
True
)
模型的屬性名需要和表的欄位名不一樣怎麼辦?
開發時遇到過一個奇怪的需求,有個其他系統的表裡包含了一個「from」欄位,這在 Python 里是關鍵字,於是只能這樣處理了:
from_ = Column("from", CHAR(10))
如何獲取欄位的長度?
Column 會生成一個很複雜的對象,想獲取長度比較麻煩,這裡以 User.name 為例:
User.name.property.columns[0].type.length
如何指定使用 InnoDB,以及使用 UTF-8 編碼?
最簡單的方式就是修改資料庫的默認配置。如果非要在代碼里指定的話,可以這樣:
class
User
(
BaseModel
)
:
__table_args__
=
{
"mysql_engine"
:
"InnoDB"
,
"mysql_charset"
:
"utf8"
}
MySQL 5.5 開始支持存儲 4 位元組的 UTF-8 編碼的字元了,iOS 里自帶的 emoji 就屬於這種。
如果是對錶來設置的話,可以把上面代碼中的 utf8 改成 utf8mb4,DB_CONNECT_STRING 里的 charset 也這樣更改。
如果對庫或欄位來設置,則還是自己寫 SQL 語句比較方便,具體細節可參考《How to support full Unicode in MySQL databases》。
不建議全用 utf8mb4 代替 utf8,因為前者更慢,索引會佔用更多空間。
如何設置外鍵約束?
from
random
import
randint
from
sqlalchemy
import
ForeignKey
class
User
(
BaseModel
)
:
__tablename__
=
"user"
id
=
Column
(
Integer
,
primary_key
=
True
)
age
=
Column
(
Integer
)
class
Friendship
(
BaseModel
)
:
__tablename__
=
"friendship"
id
=
Column
(
Integer
,
primary_key
=
True
)
user_id1
=
Column
(
Integer
,
ForeignKey
(
"user.id"
))
user_id2
=
Column
(
Integer
,
ForeignKey
(
"user.id"
))
for
i
in
xrange
(
100
)
:
session
.
add
(
User
(
age
=
randint
(
1
,
100
)))
session
.
flush
()
# 或 session.commit(),執行完後,user 對象的 id 屬性才可以訪問(因為 id 是自增的)
for
i
in
xrange
(
100
)
:
session
.
add
(
Friendship
(
user_id1
=
randint
(
1
,
100
),
user_id2
=
randint
(
1
,
100
)))
session
.
commit
()
session
.
query
(
User
).
filter
(
User
.
age
<
50
).
delete
()
執行這段代碼時,你應該會遇到一個錯誤:
sqlalchemy.exc.IntegrityError: (IntegrityError) (1451, "Cannot delete or update a parent row: a foreign key constraint fails (`ooxx`.`friendship`, CONSTRAINT `friendship_ibfk_1` FOREIGN KEY (`user_id1`) REFERENCES `user` (`id`))") "DELETE FROM user WHERE user.age < %s" (50,)
原因是刪除 user 表的數據,可能會導致 friendship 的外鍵不指向一個真實存在的記錄。在默認情況下,MySQL 會拒絕這種操作,也就是 RESTRICT。InnoDB 還允許指定 ON DELETE 為 CASCADE 和 SET NULL,前者會刪除 friendship 中無效的記錄,後者會將這些記錄的外鍵設為 NULL。
除了刪除,還有可能更改主鍵,這也會導致 friendship 的外鍵失效。於是相應的就有 ON UPDATE 了。其中 CASCADE 變成了更新相應的外鍵,而不是刪除。
而在 SQLAlchemy 中是這樣處理的:
class
Friendship
(
BaseModel
)
:
__tablename__
=
"friendship"
id
=
Column
(
Integer
,
primary_key
=
True
)
user_id1
=
Column
(
Integer
,
ForeignKey
(
"user.id"
,
ondelete
=
"CASCADE"
,
onupdate
=
"CASCADE"
))
user_id2
=
Column
(
Integer
,
ForeignKey
(
"user.id"
,
ondelete
=
"CASCADE"
,
onupdate
=
"CASCADE"
))
如何連接表?
from
sqlalchemy
import
distinct
from
sqlalchemy
.
orm
import
aliased
Friend
=
aliased
(
User
,
name
=
"Friend"
)
session
.
query
(
User
.
id
).
join
(
Friendship
,
User
.
id
==
Friendship
.
user_id1
).
all
()
# 所有有朋友的用戶
session
.
query
(
distinct
(
User
.
id
)).
join
(
Friendship
,
User
.
id
==
Friendship
.
user_id1
).
all
()
# 所有有朋友的用戶(去掉重複的)
session
.
query
(
User
.
id
).
join
(
Friendship
,
User
.
id
==
Friendship
.
user_id1
).
distinct
().
all
()
# 同上
session
.
query
(
Friendship
.
user_id2
).
join
(
User
,
User
.
id
==
Friendship
.
user_id1
).
order_by
(
Friendship
.
user_id2
).
distinct
().
all
()
# 所有被別人當成朋友的用戶
session
.
query
(
Friendship
.
user_id2
).
select_from
(
User
).
join
(
Friendship
,
User
.
id
==
Friendship
.
user_id1
).
order_by
(
Friendship
.
user_id2
).
distinct
().
all
()
# 同上,join 的方向相反,但因為不是 STRAIGHT_JOIN,所以 MySQL 可以自己選擇順序
session
.
query
(
User
.
id
,
Friendship
.
user_id2
).
join
(
Friendship
,
User
.
id
==
Friendship
.
user_id1
).
all
()
# 用戶及其朋友
session
.
query
(
User
.
id
,
Friendship
.
user_id2
).
join
(
Friendship
,
User
.
id
==
Friendship
.
user_id1
).
filter
(
User
.
id
<
10
).
all
()
# id 小於 10 的用戶及其朋友
session
.
query
(
User
.
id
,
Friend
.
id
).
join
(
Friendship
,
User
.
id
==
Friendship
.
user_id1
).
join
(
Friend
,
Friend
.
id
==
Friendship
.
user_id2
).
all
()
# 兩次 join,由於使用到相同的表,因此需要別名
session
.
query
(
User
.
id
,
Friendship
.
user_id2
).
outerjoin
(
Friendship
,
User
.
id
==
Friendship
.
user_id1
).
all
()
# 用戶及其朋友(無朋友則為 None,使用左連接)
這裡我沒提到 relationship,雖然它看上去很方便,但需要學習的內容實在太多,還要考慮很多性能上的問題,所以乾脆自己 join 吧。
為什麼無法刪除 in 操作查詢出來的記錄?
session.query(User).filter(User.id.in_((1, 2, 3))).delete()
拋出這樣的異常:
sqlalchemy.exc.InvalidRequestError: Could not evaluate current criteria in Python. Specify "fetch" or False for the synchronize_session parameter.
但這樣是沒問題的:
session.query(User).filter(or_(User.id == 1, User.id == 2, User.id == 3)).delete()
搜了下找到《Sqlalchemy delete subquery》這個問題,提到了 delete 的一個注意點:刪除記錄時,默認會嘗試刪除 session 中符合條件的對象,而 in 操作估計還不支持,於是就出錯了。解決辦法就是刪除時不進行同步,然後再讓 session 里的所有實體都過期:
session
.
query
(
User
).
filter
(
User
.
id
.
in_
((
1
,
2
,
3
))).
delete
(
synchronize_session
=
False
)
session
.
commit
()
# or session.expire_all()
此外,update 操作也有同樣的參數,如果後面立刻提交了,那麼加上 synchronize_session=False 參數會更快。
如何擴充模型的基類?
declarative_base() 會生成一個 class 對象,這個對象的子類一般都和一張表對應。如果想增加這個基類的方法或屬性,讓子類都能使用,可以有三種方法:
2. 定義一個新類,將它的方法設置為基類的方法:
雖然很拙劣,但確實能用。順便還附送了一些有用的玩意,你懂的。
2. 設置 declarative_base() 的 cls 參數:
BaseModel = declarative_base(cls=ModelMixin)
這種方法不需要執行「BaseModel.get_by_id = get_by_id」之類的代碼。不足之處就是 PyCharm 仍然無法找到這些方法的位置。
3. 設置 __abstract__ 屬性:
class
BaseModel
(
BaseModel
)
:
__abstract__
=
True
__table_args__
=
{
# 可以省掉子類的 __table_args__ 了
"mysql_engine"
:
"InnoDB"
,
"mysql_charset"
:
"utf8"
}
# ...
這種方法最簡單,也可以繼承出多個類。
如何正確使用事務?
假設有一個簡單的銀行系統,一共兩名用戶:
class
User
(
BaseModel
)
:
__tablename__
=
"user"
id
=
Column
(
Integer
,
primary_key
=
True
)
money
=
Column
(
DECIMAL
(
10
,
2
))
class
TanseferLog
(
BaseModel
)
:
__tablename__
=
"tansefer_log"
id
=
Column
(
Integer
,
primary_key
=
True
)
from_user
=
Column
(
Integer
,
ForeignKey
(
"user.id"
,
ondelete
=
"CASCADE"
,
onupdate
=
"CASCADE"
))
to_user
=
Column
(
Integer
,
ForeignKey
(
"user.id"
,
ondelete
=
"CASCADE"
,
onupdate
=
"CASCADE"
))
amount
=
Column
(
DECIMAL
(
10
,
2
))
user
=
User
(
money
=
100
)
session
.
add
(
user
)
user
=
User
(
money
=
0
)
session
.
add
(
user
)
session
.
commit
()
然後開兩個 session,同時進行兩次轉賬操作:
現在看看結果:
>>>
user1
.
money
Decimal
(
"0.00"
)
>>>
user2
.
money
Decimal
(
"100.00"
)
>>>
session
.
query
(
TanseferLog
).
count
()
2L
兩次轉賬都成功了,但是只轉走了一筆錢,這明顯不科學。
可見 MySQL InnoDB 雖然支持事務,但並不是那麼簡單的,還需要手動加鎖。
首先來試試讀鎖:
user1
=
session1
.
query
(
User
).
with_lockmode
(
"read"
).
get
(
1
)
user2
=
session1
.
query
(
User
).
with_lockmode
(
"read"
).
get
(
2
)
if
user1
.
money
>=
100
:
user1
.
money
-=
100
user2
.
money
+=
100
session1
.
add
(
TanseferLog
(
from_user
=
1
,
to_user
=
2
,
amount
=
100
))
user1
=
session2
.
query
(
User
).
with_lockmode
(
"read"
).
get
(
1
)
user2
=
session2
.
query
(
User
).
with_lockmode
(
"read"
).
get
(
2
)
if
user1
.
money
>=
100
:
user1
.
money
-=
100
user2
.
money
+=
100
session2
.
add
(
TanseferLog
(
from_user
=
1
,
to_user
=
2
,
amount
=
100
))
session1
.
commit
()
session2
.
commit
()
現在在執行 session1.commit() 的時候,因為 user1 和 user2 都被 session2 加了讀鎖,所以會等待鎖被釋放。超時以後,session1.commit() 會拋出個超時的異常,如果捕捉了的話,或者 session2 在另一個進程,那麼 session2.commit() 還是能正常提交的。這種情況下,有一個事務是肯定會提交失敗的,所以那些更改等於白做了。
接下來看看寫鎖,把上段代碼中的 『read』 改成 『update』 即可。這次在執行 select 的時候就會被阻塞了:
user1 = session2.query(User).with_lockmode("update").get(1)
這樣只要在超時期間內,session1 完成了提交或回滾,那麼 session2 就能正常判斷 user1.money >= 100 是否成立了。
由此可見,如果需要更改數據,最好加寫鎖。
那麼什麼時候用讀鎖呢?如果要保證事務運行期間內,被讀取的數據不被修改,自己也不去修改,加讀鎖即可。
舉例來說,假設我查詢一個用戶的開支記錄(同時包含餘額和轉賬記錄),可以直接把 user 和 tansefer_log 做個內連接。
但如果用戶的轉賬記錄特別多,我在查詢前想先驗證用戶的密碼(假設在 user 表中),確認相符後才查詢轉賬記錄。而這兩次查詢的期間內,用戶可能收到了一筆轉賬,導致他的 money 欄位被修改了,但我在展示給用戶時,用戶的餘額仍然沒變,這就不正常了。
而如果我在讀取 user 時加了讀鎖,用戶是無法收到轉賬的(因為無法被另一個事務加寫鎖來修改 money 欄位),這就保證了不會查出額外的 tansefer_log 記錄。等我查詢完兩張表,釋放了讀鎖後,轉賬就可以繼續進行了,不過我顯示的數據在當時的確是正確和一致的。
另外要注意的是,如果被查詢的欄位沒有加索引的話,就會變成鎖整張表了:
session1
.
query
(
User
).
filter
(
User
.
id
>
50
).
with_lockmode
(
"update"
).
all
()
session2
.
query
(
User
).
filter
(
User
.
id
<
40
).
with_lockmode
(
"update"
).
all
()
# 不會被鎖,因為 id 是主鍵
session1
.
rollback
()
session2
.
rollback
()
session1
.
query
(
User
).
filter
(
User
.
money
==
50
).
with_lockmode
(
"update"
).
all
()
session2
.
query
(
User
).
filter
(
User
.
money
==
40
).
with_lockmode
(
"update"
).
all
()
# 會等待解鎖,因為 money 上沒有索引
要避免的話,可以這樣:
money = Column(DECIMAL(10, 2), index=True)
另一個注意點是子事務。
InnoDB 支持子事務(savepoint 語句),可以簡化一些邏輯。
例如有的方法是用於改寫資料庫的,它執行時可能提交了事務,但在後續的流程中卻執行失敗了,卻沒法回滾那個方法中已經提交的事務。這時就可以把那個方法當成子事務來運行了:
def
step1
()
:
# ...
if
success
:
session
.
commit
()
return
True
session
.
rollback
()
return
False
def
step2
()
:
# ...
if
success
:
session
.
commit
()
return
True
session
.
rollback
()
return
False
session
.
begin_nested
()
if
step1
()
:
session
.
begin_nested
()
if
step2
()
:
session
.
commit
()
else
:
session
.
rollback
()
else
:
session
.
rollback
()
此外,rollback 一個子事務,可以釋放這個子事務中獲得的鎖,提高並發性和降低死鎖概率。
如何對一個欄位進行自增操作?
最簡單的辦法就是獲取時加上寫鎖:
user
=
session
.
query
(
User
).
with_lockmode
(
"update"
).
get
(
1
)
user
.
age
+=
1
session
.
commit
()
如果不想多一次讀的話,這樣寫也是可以的:
session
.
query
(
User
).
filter
(
User
.
id
==
1
).
update
({
User
.
age
:
User
.
age
+
1
})
session
.
commit
()
# 其實欄位之間也可以做運算:
session
.
query
(
User
).
filter
(
User
.
id
==
1
).
update
({
User
.
age
:
User
.
age
+
User
.
id
})
來源:孫竟
www.keakon.net/2012/12/03/SQLAlchemy使用經驗
Python開發整理髮布,轉載請聯繫作者獲得授權
【點擊成為Java大神】
※一文看懂機器學習流程(客戶流失率預測)
※用Python從零開始創建區塊鏈
TAG:Python開發 |