【问题标题】:the Memory problem about MySQL "SELECT *"MySQL "SELECT *" 的内存问题
【发布时间】:2010-12-30 01:47:19
【问题描述】:

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

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

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

但是,从我的 linux“顶部”中,我看到这个 python 进程使用了​​我 50% 的内存(总共 6GB)

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

【问题讨论】:

  • 生产数据库绑定不应该使用与请求大小成比例的内存;只要您不将结果保留在自己身边,就应该在您收到更多数据时释放旧数据。并非所有数据库绑定都能正确执行此操作,但这始终是一个错误;您应该能够从数据库后端选择无限范围,增量处理结果并且内存使用不应无限制地减少。
  • 尽管有一些数据库工具,但使用select * 几乎总是是个坏主意。您通常应该明确列出您想要的列。

标签: python mysql


【解决方案1】:

您所做的事情本身并没有错。如果内存使用量随着查询的大小而增加,则可能是以下几种情况之一:

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

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

这里有一些简单的 SQLite 代码可以重现您正在做的事情。运行时,它会创建一个包含 1500 万行的普通表,对于我正在使用的版本,它在磁盘上大约有 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()

这并不能回答您的问题,但我想明确表示您正在做的事情没有任何问题——您不需要手动分块查询,尽管最终这可能是您将要解决的问题需要。

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

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

      您要同时处理内存中的所有数据做什么?

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

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

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

      【讨论】:

      • 感谢您的回复。我想我知道我接下来应该做什么。实际上,我正在处理 old_table 中的一些数据,并将结果逐行插入 new_table。所以也许我应该把数据分成更小的集合并一个一个地处理它们。谢谢!
      • 我建议在这种情况下使用存储过程并在数据库上进行工作可能更有意义。
      【解决方案4】:

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

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

      解决这个问题(主要是使用服务器端游标类)。您在选择查询中返回的列越多,您可以预期的内存扩展就越大,因为每列都会导致创建额外的解释器元数据。不过 3Gb 似乎很大,除非你有几十列。

      【讨论】:

        猜你喜欢
        • 2015-10-19
        • 2010-11-26
        • 1970-01-01
        • 1970-01-01
        • 2020-09-30
        • 1970-01-01
        • 1970-01-01
        • 2023-04-11
        • 1970-01-01
        相关资源
        最近更新 更多