為什么PyMySQL獲取一條數(shù)據(jù)會(huì)讓內(nèi)存爆炸
當(dāng)Python 有讀寫 MySQL 數(shù)據(jù)的需求時(shí),我們經(jīng)常使用PyMySQL這個(gè)第三方庫(kù)來(lái)完成。
有時(shí)候如果一張表里面的數(shù)據(jù)非常大,但是我們只需要讀取一條數(shù)據(jù),此時(shí)我們可能會(huì)想當(dāng)然地使用cursor.fetchone()這個(gè)方法,以為這樣就真的可以只讀取一條數(shù)據(jù):
- import pymysql
- connection = pymysql.connect(host='localhost',
- user='user',
- password='passwd',
- db='db',
- charset='utf8mb4',
- cursorclass=pymysql.cursors.DictCursor)
- with connection.cursor() as cursor:
- db = 'select * from users where age > 10'
- cursor.execute(db)
- one_user = cursor.fetchone()
但實(shí)際上,上面這段代碼,與下面這段代碼沒(méi)有任何區(qū)別:
- ...
- with connection.cursor() as cursor:
- sql = 'select * from users where age > 10'
- cursor.execute(sql)
- all_users = cursor.fetchall()
- one_user = all_users[0]
這是因?yàn)椋?dāng)我們執(zhí)行到cursor.execute(sql)的時(shí)候,PyMySQL就已經(jīng)把表里面所有的數(shù)據(jù)讀取到內(nèi)存中了。而后面的cursor.fetchall()或者cursor.fetchone()只不過(guò)是從內(nèi)存中返回全部數(shù)據(jù)還是返回1條數(shù)據(jù)而已。
我們來(lái)看PyMySQL的源代碼[1]。在cursor.execute()方法代碼如下圖所示:

其中第163行調(diào)用了self._query方法。我們?cè)偃サ竭@個(gè)方法里面:
看到代碼第322行,調(diào)用了self._do_get_result()方法。我們?cè)偃ミ@個(gè)方法里面看看:
注意代碼第342行,此時(shí)已經(jīng)把所有數(shù)據(jù)存放到了self._rows列表中。
現(xiàn)在我們來(lái)看cursor.fetchone()方法:
可以看到,這里不過(guò)是從列表里面根據(jù)下標(biāo)讀取一條數(shù)據(jù)出來(lái)而已。
再看cursor.fetchall()方法:
如果之前先多次調(diào)用過(guò)cursor.fetchone(),那么self.rownumber會(huì)持續(xù)增加。而調(diào)用cursor.fetchall()時(shí),跳過(guò)之前已經(jīng)返回過(guò)的數(shù)據(jù),直接返回剩下的全部數(shù)據(jù)即可。如果之前沒(méi)有調(diào)用過(guò)cursor.fetchone(),那么直接返回全部數(shù)據(jù)。
所以,單純使用cursor.fetchone()并不能節(jié)省內(nèi)存,如果表里面的數(shù)據(jù)非常大,還是會(huì)有內(nèi)存爆炸的危險(xiǎn)。
那么真正的解決辦法是什么呢?真正的解決辦法在創(chuàng)建數(shù)據(jù)庫(kù)連接的時(shí)候指定游標(biāo)類型。pymysql.connect有一個(gè)參數(shù)叫做cursorclass,把它的值設(shè)定為pymysql.SSDictCursor即可解決問(wèn)題。
我們來(lái)看一下如何正確使用它:
- import pymysql
- connection = pymysql.connect(host='localhost',
- user='user',
- password='passwd',
- db='db',
- charset='utf8mb4',
- cursorclass=pymysql.cursors.SSDictCursor)
- with connection.cursor() as cursor:
- db = 'select * from users where age > 10'
- cursor.execute(db)
- for row in cursor:
- print('對(duì) cursor 直接進(jìn)行迭代,每循環(huán)一次,從數(shù)據(jù)庫(kù)讀取一條數(shù)據(jù)。不會(huì)提前把所有數(shù)據(jù)讀取到內(nèi)存中。')
- print(row['name'])