減輕內存負擔,在 pymysql 中使用 SSCursor 查詢結果集較大的 SQL

前言

默認情況下曙寡,使用 pymysql 查詢數據使用的游標類是 Cursor糠爬,比如:

import pymysql.cursors

# 連接數據庫
connection = pymysql.connect(host='localhost',
                             user='user',
                             password='passwd',
                             db='db',
                             charset='utf8mb4')

try:
    with connection.cursor() as cursor:
        # 讀取所有數據
        sql = "SELECT `id`, `password` FROM `users` WHERE `email`=%s"
        cursor.execute(sql, ('webmaster@python.org',))
        result = cursor.fetchall()
        print(result)
finally:
    connection.close()

這種寫法會將查詢到的所有數據寫入內存中,若在結果較大的情況下举庶,會對內存造成很大的壓力执隧,所幸 pymysql 實現了一種 SSCursor 游標類,它允許將查詢結果按需返回户侥,而不是一次性全部返回導致內存使用量飆升镀琉。

SSCursor

官方文檔的解釋為:

Unbuffered Cursor, mainly useful for queries that return a lot of data,
or for connections to remote servers over a slow network.

Instead of copying every row of data into a buffer, this will fetch
rows as needed. The upside of this is the client uses much less memory,
and rows are returned much faster when traveling over a slow network
or if the result set is very big.

There are limitations, though. The MySQL protocol doesn't support
returning the total number of rows, so the only way to tell how many rows
there are is to iterate over every row returned. Also, it currently isn't
possible to scroll backwards, as only the current row is held in memory.

大致翻譯為:無緩存的游標,主要用于查詢大量結果集或網絡連接較慢的情況蕊唐。不同于普通的游標類將每一行數據寫入緩存的操作屋摔,該游標類會按需讀取數據,這樣的好處是客戶端消耗的內存較小替梨,而在網絡連接較慢或結果集較大的情況下钓试,數據的返回也會更快署尤。當然,缺點就是它不支持返回結果的行數(也就是調用 rowcount 屬性將不會得到正確的結果亚侠,一共有多少行數據則需要全部迭代完成才能知道)曹体,當然它也不支持往回讀取數據(這也很好理解,畢竟是生成器嘛)硝烂。

它的寫法如下:

from pymysql.cursors import SSCursor

connection = pymysql.connect(host='localhost',
                             user='user',
                             password='passwd',
                             db='db',
                             charset='utf8mb4')

# 創(chuàng)建游標
cur = connection.cursor(SScursor)
cur.execute('SELECT * FROM test_table')

# 讀取數據
# 此時的 cur 對內存消耗相對 Cursor 類來說簡直微不足道
for data in cur:
    print(data)

本質上對所有游標類的迭代都是在不斷的調用 fetchone 方法箕别,不同的是 SSCursor 對 fetchone 方法的實現不同罷了。這一點查看源碼即可發(fā)現:
Cursor 類 fetchone 方法源碼(可見它是在根據下標獲取列表中的某條數據):

image

SSCursor 類 fetchone 方法源碼(讀取數據并不做緩存):

pylittleimage-20201025192825032.png

跳坑

當然滞谢,如果沒有坑就沒必要為此寫一篇文章了串稀,開開心心的用著不香嗎。經過多次使用狮杨,發(fā)現在使用 SSCursor 游標類(以及其子類 SSDictCursor)時母截,需要特別注意以下兩個問題:

1. 讀取數據間隔問題

每條數據間的讀取間隔若超過 60s,可能會造成異常橄教,這是由于 MySQL 的 NET_WRITE_TIMEOUT 設置引起的錯誤(該設置值默認為 60)清寇,如果讀取的數據有處理時間較長的情況,那么則需要考慮更改 MySQL 的相關設置了护蝶。(tips: 使用 sql SET NET_WRITE_TIMEOUT = xx 更改該設置或修改 MySQL配置文件)

2. 讀取數據時對數據庫的其它操作行為

因為 SSCursor 是沒有緩存的华烟,只要結果集沒有被讀取完成,就不能使用該游標綁定的連接進行其它數據庫操作(包括生成新的游標對象)持灰,如果需要做其它操作盔夜,應該使用新的連接。比如:

from pymysql.cursors import SSCursor

def connect():
    connection = pymysql.connect(host='localhost',
                                 user='user',
                                 password='passwd',
                                 db='db',
                                 charset='utf8mb4')
    return connection

conn1 = connect()
conn2 = connect()

cur1 = conn1.cursor(SScursor)
cur2 = conn1.cursor()

with conn1.cursor(SSCursor) as ss_cur, conn2.cursor() as cur:
    try:
        ss_cur.execute('SELECT id, name FROM test_table')

        for data in ss_cur:
            # 使用 conn2 的游標更新數據
            if data[0] == 15:
                cur.execute('UPDATE tset_table SET name="kingron" WHERE id=%s', args=[data[0])

            print(data)
    finally:
        conn1.close()
        conn2.close()

參考

  1. Cursor Objects — PyMySQL 0.7.2 documentation
  2. Using SSCursor (streaming cursor) to solve Python using pymysql to query large amounts of data leads to memory usage is too high
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末堤魁,一起剝皮案震驚了整個濱河市喂链,隨后出現的幾起案子,更是在濱河造成了極大的恐慌妥泉,老刑警劉巖椭微,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異涛漂,居然都是意外死亡赏表,警方通過查閱死者的電腦和手機检诗,發(fā)現死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進店門匈仗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人逢慌,你說我怎么就攤上這事悠轩。” “怎么了攻泼?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵火架,是天一觀的道長鉴象。 經常有香客問我,道長何鸡,這世上最難降的妖魔是什么纺弊? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮骡男,結果婚禮上淆游,老公的妹妹穿的比我還像新娘。我一直安慰自己隔盛,他們只是感情好犹菱,可當我...
    茶點故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著吮炕,像睡著了一般腊脱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上龙亲,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天陕凹,我揣著相機與錄音,去河邊找鬼鳄炉。 笑死捆姜,一個胖子當著我的面吹牛,可吹牛的內容都是我干的迎膜。 我是一名探鬼主播泥技,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼磕仅!你這毒婦竟也來了珊豹?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤榕订,失蹤者是張志新(化名)和其女友劉穎店茶,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體劫恒,經...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡贩幻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了两嘴。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丛楚。...
    茶點故事閱讀 38,664評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖憔辫,靈堂內的尸體忽然破棺而出趣些,到底是詐尸還是另有隱情,我是刑警寧澤贰您,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布坏平,位于F島的核電站拢操,受9級特大地震影響,放射性物質發(fā)生泄漏舶替。R本人自食惡果不足惜令境,卻給世界環(huán)境...
    茶點故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望顾瞪。 院中可真熱鬧展父,春花似錦、人聲如沸玲昧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽孵延。三九已至吕漂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間尘应,已是汗流浹背惶凝。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留犬钢,地道東北人苍鲜。 一個月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像玷犹,于是被迫代替她去往敵國和親混滔。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,554評論 2 349