关于MySQL“SELECT *”的内存问题

时间:2010-12-30 01:47:19

标签: python mysql

我是MySQL的新手,我对内存有疑问。

我有一个200mb的表(MyISAM,2,000,000行),我尝试将所有这些表加载到 记忆。

我使用python(实际上是python中的MySQLdb)和sql:SELECT * FROM table

然而,从我的linux“top”我看到这个python进程使用了​​50%的内存(总共6GB)

我很好奇为什么它只为一个200 mb的表使用大约3GB的内存。 提前谢谢!

4 个答案:

答案 0 :(得分:2)

你正在做的事情本身没有任何错误。如果内存使用量随着查询的大小而扩大,那么就会发生以下几种情况之一:

  • 您正在泄露对您收到的结果的引用;例如,将它们放在某个列表中。我怀疑你是否知道你是否这样做。
  • 从查询中读取新行时,数据库绑定或基础库不会释放先前行的内存。这通常是一个错误。如果你有调试功能,它可能会正常发生,但默认情况下不应该发生。

请注意,底层库可能会缓存一定数量的数据,因此您可能会看到大量内存使用,但除非配置出现灾难性错误,否则它不应为3GB。

这是一些简单的SQLite代码,可以重现您正在做的事情。运行时,它会创建一个包含一千五百万行的普通表,对于我正在使用的版本,它在磁盘上大约为180 MB。然后选择所有这些数据,丢弃结果,然后睡觉,以便检查结果。在我的系统上生成的进程仅使用15 MB。

(请注意,我使用单独的调用运行create_dbread_db次传递;创建数据库需要一些时间。)

SQLite可以处理这个问题,任何生产服务器支持的数据库,如MySQL和Postgresql也应该能够处理。 SELECT结果是数据流,数据库应该能够轻松处理无限大小的流。

import sqlite3
def create_db(conn):
    c = conn.cursor()
    c.execute('create table test (i integer)')
    conn.commit()
    max_val = 15000000
    chunk = 1000000
    for start in xrange(0, max_val, chunk):
        print "%i ..." % start
        for i in xrange(start, start + chunk):
            c = conn.cursor()
            c.execute('insert into test (i) values (?)', (i,))
        conn.commit()

def read_db(conn):
    c = conn.cursor()
    c.execute('select * from test')
    for x in xrange(15000000):
        c.fetchone()

    print "Done"

    # Sleep forever, to examine memory usage:
    while True:
        time.sleep(1)

def go():
    conn = sqlite3.connect('test.db')

    # Pick one:
    create_db(conn)
    # read_db(conn)

if __name__ == "__main__":
    go()

这不能回答你的问题,但是我想明确表示你正在做的事情没有任何问题 - 你不应该手动查询,尽管最后这可能是你的解决方法需要。

答案 1 :(得分:1)

在几乎任何脚本语言中,变量总是会占用比实际内容所暗示的内存更多的内存。 INT可能是32或64位,这表明它需要4或8个字节的内存,但它需要16或32个字节(从我的帽子中提取数字),因为语言解释器必须将各种元数据附加到该值上。方式。

数据库可能只需要200兆字节的原始存储空间,但一旦您考虑了元数据,它肯定会占用更多。

答案 2 :(得分:0)

这几乎肯定是一个糟糕的设计。

你一下子对内存中的所有数据做了什么?

如果是一个用户,为什么不缩小尺寸以便支持多个用户?

如果您正在中间层进行计算,是否可以将工作转移到数据库服务器,这样您就不必将所有数据都带入内存?

你知道你可以做到这一点,但更大的问题是(1)为什么? (2)你还能做什么?我们需要更多背景来回答这些问题。

答案 3 :(得分:0)

这是Marc B的答案和MySQLdb中的陷阱(不是错误)的组合。 MySQLdb中的默认光标是客户端游标,这意味着客户端库将整个结果集编组在客户端进程的内存中。见

的答案

How to get a row-by-row MySQL ResultSet in python

解决此问题(主要使用服务器端游标类)。您在选择查询中返回的列越多,您可以预期的内存扩展就越大,因为每列会导致创建其他解释器元数据。 3Gb看起来很大,除非你有几十个专栏。