【MySQL】一个MySQL读取数据中的小优化

【MySQL】一个MySQL读取数据的小优化

当我们使用ODBC/JDBC/pymysql等模块在不同的语言平台上运行MySQL时,常需要对MySQL数据库中的内容进行读取。当读取的数据量较大时,可能会产生内存占用过高的问题。

常规的连接——Python

Python中常用pymysql库来执行SQL语句操作数据库,比如下面这段代码,创建一个游标用以执行读取db1库中的person表中的第一行信息的操作:

from pymysql import connect
con = connect(host='localhost', user='root', password='377600', db='db1', charset='utf8')
with con.cursor() as cursor:
    db = 'select *from person;'
    cursor.execute(db)
    one_user=cursor.fetchone()
    print(one_user)

我们再来看,如果修改倒数第二行代码为

from pymysql import connect
con = connect(host='localhost', user='root', password='377600', db='db1', charset='utf8')
with con.cursor() as cursor:
    db = 'select *from person;'
    cursor.execute(db)
    one_user=cursor.fetchall()
    print(one_user)

即将fetchone改成fechall,那么将会以列表的形式输出表内的所有数据局。
通常我们肯定会认为,第一段代码在执行时所需空间远小于第二段代码,毕竟数据规模第一个远小于第二个嘛!
可实际上,真不是这样!二者操作的空间开销其实一样的
问题出在哪呢?我们来看下源码:

def fetchone(self):
        """Fetch the next row"""
        self._check_executed()
        if self._rows is None or self.rownumber >= len(self._rows):
            return None
        result = self._rows[self.rownumber]
        # 请注意下这里,采用了建立在读取了全体数据基础上的一个搜索
        self.rownumber += 1
        return result

以及

 def fetchall(self):
        """Fetch all the rows"""
        self._check_executed()
        if self._rows is None:
            return ()
        if self.rownumber:
            result = self._rows[self.rownumber:]
        else:
            result = self._rows
        self.rownumber = len(self._rows)
        return result

本质上fetchone()和fectall()方法都是在一个存储了表中所有数据的列表中进行搜索,只是前者返回其中一行数据,后者全部返回。
也就是说,当执行execute()中的SQL语句时,python本身就已经将所有数据暂时存储到内存中,供下一步调用。
同时,需要注意的是,如果表中数据非常大,多次使用fetchone()极有可能导致内存爆炸。
那么怎么解决呢?

优化

pymysql.connect中有一个参数,cursorclass,是connect类的构造方法的一个参数。通过设置其参数改一下其读取表
中数据的方式为每操作一次迭代地读取一次:

from pymysql import connect
import pymysql
con = connect(host='localhost', user='root', password='377600', db='db1', charset='utf8', cursorclass=pymysql.cursors.SSDictCursor)
with con.cursor() as cursor:
    db = 'select *from person;'
    cursor.execute(db)
    for row in cursor:
        print(row)

这样,我们修改了数据的读取方式。通过迭代地按需求读取输出,比fetchone节省了许多内存空间

总结

常用的游标在创建时,由于对于默认的构造方法中的参数未作修改,因此读取数据时比较暴力。
而文中提及方法采用了“流式游标”法,迭代访问,比较节约成本。
当然流式也有自身缺点——由于是迭代地进行访问,类似于爬虫,当数据量过大时,要注意timeout的问题,一般不要超过60s
Java的JDBC中也有类似的操作,不过目前我还没发现(而且得益于JVM良好的GC机制,一般不会出现此类内存危机)。
当然pymysql库的类中还有许多隐藏的玄机,日后待我发现后会继续更新到博客中。

发布了29 篇原创文章 · 获赞 18 · 访问量 3996

猜你喜欢

转载自blog.csdn.net/weixin_44522586/article/details/104638248