最近用 Flask 寫了幾個接口部署在服務器上,然后用 Pytest 來做測試,但遇到了問題,搞了大半天才把問題解決。
問題場景及原因
問題大概是這樣的,我在本地環境用 Pytest 寫代碼來對服務器上 Flask 的接口進行測試,在測試刪除接口的時候,第一步我在 Pytest 中會通過SQL插入數據到MySQL數據庫,第二步再調刪除接口完成刪除用戶。最后執行 Pytest 代碼沒跑通過,會返回用戶不存在,但這個情況到數據庫里查看,發現該用戶實際是存在於MySQL中。
首先,這個接口是沒啥大問題的,調注冊接口或手工插入數據,再手工請求訪問接口可以正常完成刪除,只是我在 Pytest 代碼中通過SQL插入數據會出現這個問題。
其次,懷疑自己 Pytest 代碼寫得有問題,但檢查后發現沒問題,並且加了查詢操作發現能查回這個新插入的數據。
最后,在各種排查后,在 Flask 接口服務中,也加了查詢操作,但發現壓根沒查回這個新插入的數據。
於是,感覺接口環境下通過 Python 連接 pymysql ,查詢數據庫時,獲取到的似乎不是最新數據,所以調用戶刪除接口才會提示用戶不存在。接着,在網上查了下資料,發現是事務的隔離級別導致的這個問題。
MySQL默認事務隔離級別是
REPEATABLE READ
,當我在本地 Pytest 代碼中利用SQL插入用戶數據並提交 commit 后,接口環境下的事務A不會讀取到我本地環境下的事務B更新的信息,即便事務B已提交,而事務A每次查詢到的數據都是最開始創建事務查詢到結果的快照,事務A一直沒進行更新。更詳細的解釋說明,可參考文末列出的網上資料。
問題代碼
import pymysql
class MysqlDb():
def __init__(self, host, port, user, passwd, db):
# 建立數據庫連接
self.conn = pymysql.connect(
host=host,
port=port,
user=user,
passwd=passwd,
db=db
)
# 通過 cursor() 創建游標對象,並讓查詢結果以字典格式輸出
self.cur = self.conn.cursor(cursor=pymysql.cursors.DictCursor)
def __del__(self): # 對象資源被釋放時觸發,在對象即將被刪除時的最后操作
# 關閉游標
self.cur.close()
# 關閉數據庫連接
self.conn.close()
def select_db(self, sql):
"""查詢"""
# 檢查連接是否斷開,如果斷開就進行重連
self.conn.ping(reconnect=True)
# 使用 execute() 執行sql
self.cur.execute(sql)
# 使用 fetchall() 獲取查詢結果
data = self.cur.fetchall()
return data
def execute_db(self, sql):
"""更新/新增/刪除"""
try:
# 檢查連接是否斷開,如果斷開就進行重連
self.conn.ping(reconnect=True)
# 使用 execute() 執行sql
self.cur.execute(sql)
# 提交事務
self.conn.commit()
except Exception as e:
print("操作出現錯誤:{}".format(e))
# 回滾所有更改
self.conn.rollback()
解決辦法
根據以上說明,要解決當前這個問題,我們可以通過以下方式來處理。
方法1:修改MySQL的事務隔離級別
方法2:每次查詢操作后,都進行commit()
提交事務。
方法3:Python創建pymysql連接時,設置autocommit=True
,即讓其操作后自動提交事務。
在這里,我們最好在方法2和方法3任選一個方法來處理就行了,不建議去修改MySQL的事務隔離級別。
- 每次查詢后進行提交事務
def select_db(self, sql):
"""查詢"""
# 檢查連接是否斷開,如果斷開就進行重連
self.conn.ping(reconnect=True)
# 使用 execute() 執行sql
self.cur.execute(sql)
# 使用 fetchall() 獲取查詢結果
data = self.cur.fetchall()
# 提交事務
self.conn.commit()
return data
- 設置 autocommit=True 自動提交事務
def __init__(self, host, port, user, passwd, db):
# 建立數據庫連接
self.conn = pymysql.connect(
host=host,
port=port,
user=user,
passwd=passwd,
db=db,
autocommit=True
)
# 通過 cursor() 創建游標對象,並讓查詢結果以字典格式輸出
self.cur = self.conn.cursor(cursor=pymysql.cursors.DictCursor)
使用以上方法后,再次測試,發現問題已順利得到解決。
參考資料:
為什么pymysql重連后才能查到被其他地方修改的數據 pymysql緩存?
記一次pymysql查詢不到表中最新插入的數據的問題