MySQL "SELECT *" 的内存问题
我刚接触MySQL,有个关于内存的问题。
我有一个200MB的表(使用MyISAM,里面有2,000,000行数据),我想把它全部加载到内存中。
我用的是Python(实际上是Python中的MySQLdb),执行的SQL语句是:SELECT * FROM table
。
不过,从我的Linux系统的“top”命令中,我看到这个Python进程使用了我一半的内存(我总共有6GB内存)。
我很好奇,为什么仅仅是一个200MB的表,竟然会占用大约3GB的内存。提前谢谢大家!
4 个回答
这几乎肯定是个糟糕的设计。
你为什么要一次性把这么多数据都放到内存里呢?
如果这些数据是针对一个用户的,为什么不把数据量缩小一点,这样就能支持多个用户了?
如果你是在中间层进行计算,能不能把这些工作转移到数据库服务器上,这样就不需要把所有数据都加载到内存里?
你知道这样做是可以的,但更大的问题是(1)为什么要这样做?(2)还有什么其他的做法?我们需要更多的背景信息才能回答这些问题。
几乎在任何脚本语言中,一个变量所占用的内存总是比它实际内容所暗示的要多。比如,一个整数(INT)可能是32位或64位,这意味着它应该需要4个字节或8个字节的内存,但实际上它可能会占用16个字节或32个字节(这是我随便举的例子),因为语言解释器需要在这个值上附加各种元数据。
数据库可能只需要200兆字节的原始存储空间,但一旦考虑到元数据,它占用的空间肯定会多得多。
你现在做的事情本身没有什么问题。如果你发现内存使用量随着查询的大小在增加,那可能有几个原因:
- 你可能在某个地方保留了查询结果的引用,比如把它们放在一个列表里。如果是这样的话,你应该会意识到。
- 数据库的绑定或底层库在读取新行时没有释放之前行占用的内存。这通常是个bug。如果你开启了调试功能,可能会出现这种情况,但正常情况下不应该这样。
需要注意的是,底层库可能会缓存一定量的数据,这可能会导致你看到较高的内存使用,但只要配置没有严重问题,内存使用不应该达到3GB。
下面是一些简单的SQLite代码,模拟了你正在做的事情。运行后,它会创建一个包含一千五百万行的小表,这在我使用的版本中大约占用180MB的磁盘空间。然后它会选择所有这些数据,但不保存结果,并暂停一段时间,让你可以查看结果。在我的系统上,最终的进程只使用了15MB的内存。
(注意,我分别运行了create_db
和read_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()
这虽然没有直接回答你的问题,但我想让你明白你做的事情没有问题——你不需要手动分块查询,尽管最终你可能还是需要这样做作为解决办法。