【问题标题】:Why does 60GB memory disappear on a MySQL connector fetchall()?为什么 60GB 内存在 MySQL 连接器 fetchall() 上消失?
【发布时间】:2019-02-07 12:31:58
【问题描述】:

MySQL 5.7.18
Python 2.7.5
熊猫 0.17.1
CentOS 7.3

一个 MySQL 表:

CREATE TABLE test (
  id varchar(12)
) ENGINE=InnoDB;

大小为 10GB。

select round(((data_length) / 1024 / 1024 / 1024)) "GB"
from information_schema.tables 
where table_name = "test"

10GB

盒子有 250GB 内存:

$ free -hm
              total        used        free      shared  buff/cache   available
Mem:           251G         15G        214G        2.3G         21G        232G
Swap:          2.0G        1.2G        839M

选择数据:

import psutil
print '1 ' + str(psutil.phymem_usage())

import os
import sys
import time
import pyodbc 
import mysql.connector
import pandas as pd
from datetime import date
import gc
print '2 ' + str(psutil.phymem_usage())

db = mysql.connector.connect({snip})
c = db.cursor()
print '3 ' + str(psutil.phymem_usage())

c.execute("select id from test")
print '4 ' + str(psutil.phymem_usage())

e=c.fetchall()
print 'getsizeof: ' + str(sys.getsizeof(e))
print '5 ' + str(psutil.phymem_usage())

d=pd.DataFrame(e)
print d.info()
print '6 ' + str(psutil.phymem_usage())

c.close()
print '7 ' + str(psutil.phymem_usage())

db.close()
print '8 ' + str(psutil.phymem_usage())

del c, db, e
print '9 ' + str(psutil.phymem_usage())

gc.collect()
print '10 ' + str(psutil.phymem_usage())

time.sleep(60)
print '11 ' + str(psutil.phymem_usage())

输出:

1 svmem(total=270194331648L, available=249765777408L, percent=7.6, used=39435464704L, free=230758866944L, active=20528222208, inactive=13648789504, buffers=345387008L, cached=18661523456)
2 svmem(total=270194331648L, available=249729019904L, percent=7.6, used=39472222208L, free=230722109440L, active=20563484672, inactive=13648793600, buffers=345387008L, cached=18661523456)
3 svmem(total=270194331648L, available=249729019904L, percent=7.6, used=39472222208L, free=230722109440L, active=20563484672, inactive=13648793600, buffers=345387008L, cached=18661523456)
4 svmem(total=270194331648L, available=249729019904L, percent=7.6, used=39472222208L, free=230722109440L, active=20563484672, inactive=13648793600, buffers=345387008L, cached=18661523456)
getsizeof: 1960771816
5 svmem(total=270194331648L, available=181568315392L, percent=32.8, used=107641655296L, free=162552676352L, active=88588271616, inactive=13656334336, buffers=345395200L, cached=18670243840)
<class 'pandas.core.frame.DataFrame'>
Int64Index: 231246823 entries, 0 to 231246822
Data columns (total 1 columns):
0    object
dtypes: object(1)
memory usage: 3.4+ GB
None
6 svmem(total=270194331648L, available=181571620864L, percent=32.8, used=107638353920L, free=162555977728L, active=88587603968, inactive=13656334336, buffers=345395200L, cached=18670247936)
7 svmem(total=270194331648L, available=181571620864L, percent=32.8, used=107638353920L, free=162555977728L, active=88587603968, inactive=13656334336, buffers=345395200L, cached=18670247936)
8 svmem(total=270194331648L, available=181571620864L, percent=32.8, used=107638353920L, free=162555977728L, active=88587603968, inactive=13656334336, buffers=345395200L, cached=18670247936)
9 svmem(total=270194331648L, available=183428308992L, percent=32.1, used=105781678080L, free=164412653568L, active=86735921152, inactive=13656334336, buffers=345395200L, cached=18670260224)
10 svmem(total=270194331648L, available=183428308992L, percent=32.1, used=105781678080L, free=164412653568L, active=86735921152, inactive=13656334336, buffers=345395200L, cached=18670260224)
11 svmem(total=270194331648L, available=183427203072L, percent=32.1, used=105782812672L, free=164411518976L, active=86736560128, inactive=13656330240, buffers=345395200L, cached=18670288896)

我什至删除了数据库连接并调用了垃圾回收。

一个 10GB 的表怎么会占用我 60GB 的内存?

【问题讨论】:

  • 如果将DataFrame 创建和c.fetchall() 分开并在两者之间打印内存使用情况会怎样?
  • 我按照你说的把这两个项目分开了,并替换了上面问题中的代码和输出。似乎所有的内存都在 fetchall() 处立即丢失。
  • 您的 fetchall 正在返回 2.31 亿行,这似乎是个坏主意。您可以将获取大小设置为更合理的值。另外,MySQL 连接器是否有上下文处理程序?
  • 你能在e 元组列表的元素上做一个getsizeof 吗?而且也没有元组的每个元素?

标签: python memory-management memory-leaks mysql-connector-python


【解决方案1】:

简短的回答:python 数据结构的内存开销。

您有一个约 2.31 亿行的表,占用约 10GB,因此每行大约有 4 个字节。

fetchall 将其翻译成这样的元组列表:

[('abcd',), ('1234',), ... ]

您的列表有 ~231M 元素并使用 ~19GB 内存:平均每个元组使用 8.48 字节。

$ python
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys

一个元组:

>>> a = ('abcd',)
>>> sys.getsizeof(a)
64

一个元组的列表:

>>> al = [('abcd',)]
>>> sys.getsizeof(al)
80

两个元组的列表:

>>> al2 = [('abcd',), ('1234',)]
>>> sys.getsizeof(al2)
88

一个包含 10 个元组的列表:

>>> al10 = [ ('abcd',) for x in range(10)]
>>> sys.getsizeof(al10)
200

包含 1M 元组的列表:

>>> a_realy_long = [ ('abcd',) for x in range(1000000)]
>>> sys.getsizeof(a_realy_long )
8697472

几乎是我们的数字:列表中每个元组 8.6 个字节。

很遗憾,您在这里无能为力:mysql.connector 选择数据结构,dict cursor 将使用更多内存。

如果你需要减少内存使用,你必须使用 fetchmany 和合适的大小参数。

【讨论】:

  • 您的回答表明 10GB 的表在内存中变成了 19GB。这听起来像是增加了 9GB。这如何解释 60GB 消失的原因?
  • 对——可能是mysql.connector的内部使用;你能试试fetchmany 页面大小为 100M 吗?
  • 我重写为使用 fetchmany() 和 100M 的页面大小。结果是使用的内存减少到 23%,这意味着使用了 40GB。这是一个改进,但为什么 10GB 表使用 40GB 内存仍然是个谜。
【解决方案2】:

编辑: pd.read_sql 只接受 SQLAlchemy 连接。首先使用 SQLAlchemy 中的 create_engine 连接到您的数据库:

from sqlalchemy import create_engine
engine = create_engine('mysql://database')

然后在结果对象上调用.connect()

connection = engine.connect()

将该连接传递给pd.read_sql

df = pd.read_sql("select id from test", connection)

这应该会减少您的内存占用。

您在尝试上述方法后介意发布内存使用结果吗?

【讨论】:

  • 我重写了脚本以使用您所说的 pd.read_sql() ,但内存使用结果是相同的。顺便说一句,我让 pd.read_sql() 在不使用 SQLAlchemy 的情况下工作。
  • 在原始脚本中,问题从我进入 Pandas 之前的“e=c.fetchall()”开始。所以问题不在于 Pandas,而是在那之前。
  • @davidjhp 对。我对你没有任何进一步的建议,因为我不熟悉 Pandas 之外的实现。也许这个答案(和问题)会带你到某个地方:stackoverflow.com/a/17861221
猜你喜欢
  • 1970-01-01
  • 2011-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多