Question

有没有一种非内存密集型的方法可以从MariaDB数据库中提取大型数据集并将其直接导入Python(通过python-mariadb连接器)?

Edit: Solution

关注@Georg Richter的solution below,我们可以这样做:

connection = mariadb.connect(user='<username>', host='localhost', database='my_database')
cursor = connection.cursor(buffered=False)
query = 'SELECT column_1,column_2 FROM my_table'
cursor.execute(query)
df = pd.DataFrame({col:[] for col in range(number_of_columns_you_extract)})
size = 2**23
while rows := cursor.fetchmany(size):
    df = pd.concat([df, pd.DataFrame(rows)], copy=False)

出于某种原因,这会将我的int值强制转换为Float.但内存使用量永远不会超过10 GB.

Context

我在Linux上的MariaDB中有一个大型数据库.我经常不得不将它的一部分导入到Python中进行进一步的计算.就内存使用而言,MariaDB相当经济,但Python并非如此.虽然在Python中保存数据的内存消耗较少(例如,作为具有适用于所有列的适当数据类型的Pandas DataFrame),但通常的工作流程涉及多次复制数据,并且在某些阶段内存需求是巨大的.

Simple Example

import pandas as pd
import mariadb
import gc

connection = mariadb.connect(user='<username>', host='localhost', database='my_database')
cursor = connection.cursor(dictionary=False)
query = 'SELECT column_1,column_2 FROM my_table'
cursor.execute(query)
rows = cursor.fetchall()
del cursor
gc.collect()
df = pd.DataFrame(rows)
del rows
gc.collect()

该示例提取5亿行和两列,所有值都是整数.

超过cursor.execute(query)后,内存使用量约为35 GB.在rows = cursor.fetchall()之后,它会上升到90 GB左右.在将数据转换为Pandas DataFrame并删除其他对象后,内存需求降至约8 GB.我使用top监视内存使用情况.

我不确定数据是如何保存在Cursor对象中的.I rows,它是一个元组列表.(如果您使用cursor = connection.cursor(dictionary=True)来保留列名,那么它将是一个字典列表,每个字典都将列名作为字符串.不用说,在这种情况下,内存需求远远超过上面报告的90 GB.)

Ideas for solutions

  • go 掉保存数据的两个中间阶段(cursor.execute()之后和cursor.fetchall()之后),直接保存到Pandas DataFrame中.Python-MariaDB似乎不提供这一功能.在我看来,这并不是一件容易实现的事情.

  • 以块为单位导入数据.在这种情况下,我担心(1)可能的数据类型不一致,(2)最终将数据帧连接成一个数据帧时的内存需求,(3)在更复杂的MariaDB查询(特别是将查询结果拆分成块的特殊实现)的情况下可能引入的不一致,以及(4)计算时间,如果在最坏的可能情况下,查询可能不得不多次运行,因为有Ara块.

  • 将查询结果保存到硬盘中,并将其读回Pandas 中.这基本上意味着要解决python-MariaDB连接器已经实现的所有问题,并用手动修改来代替它.此外,这可能会导致硬盘的写入权限出现问题.(我的MariaDB配置中有ProtectHome=true,这将防止MariaDB写入/home等中的任何位置.出于安全原因,这是大多数Linux的标准.可以通过绑定挂载或允许用户处理/home以外的目录来解决这个问题,但我认为这两个都不是特别好的主意.)

  • 在@yothegiitou的 comments 之后,您可以通过将MariaDB连接直接插入pd.read_sql并使用chunksize参数来稍微提高内存使用率.内存使用量仍然很大(生成器对象的内存使用量为30 GB,在创建DataFrame的过程中内存使用量增加了10 GB),但只有我最初方法的一半左右.另外一个警告是,pd.read_sql会抛出UserWarning: pandas only supports SQLAlchemy connectable...个警告.这可能意味着,也可能不意味着在某些情况下存在任何真正的问题--对于我审理的那个 case ,它看起来很好.代码将为:

    chunks = pd.read_sql(statement, connection, chunksize=50000000) dfx = pd.concat(list(chunks))

Additional Information

MariaDB版本是11.2.2,Python版本是3.11.6,Python-MariaDB连接器版本是1.1.8,Linux是带内核6.7.4的Arch Linux.

推荐答案

以下是一些内存消耗较少的建议:

Client/Server protocol/communication

使用无缓冲游标,缓冲游标将分配额外的内存来存储检索到的数据:

cursor= connection.cursor(buffered=False)

判断两个整型列的平均长度:

SELECT AVG(LENGTH(col1)), AVG(LENGTH(col2)) FROM my_table

如果BIGINT列的平均长度大于8(或INT列大于4),则使用BINARY协议:

cursor = connection.cursor(buffered=False, binary=True)

在二进制协议中,服务器不会将值作为字符串发送.例如,Bigint值17592186044416(14位)将作为8字节二进制发送.

Don't use fetchall()

在C中,一个普通的整数值需要4个字节,而Python将其存储在PyLong类型的PythonObject中.除了用于引用计数和基本对象的16个字节之外,还需要额外的内存来存储Python的bignum实现的值.这意味着,对于每个Int变量,必须分配比实际值所需的内存至少多4倍的内存.

因此,要一次获取所有数据,请将它们分成部分获取,并将行附加到DataFrame:

size= 1024
while rows:=cursor.fetchmany(size)
    df.append(rows)

Python相关问答推荐

根据条件将新值添加到下面的行或下面新创建的行中

max_of_three使用First_select、second_select、

在Python Attrs包中,如何在field_Transformer函数中添加字段?

使可滚动框架在tkinter环境中看起来自然

在Python argparse包中添加formatter_class MetavarTypeHelpFormatter时, - help不再工作""""

如何从数据库上传数据到html?

循环浏览每个客户记录,以获取他们来自的第一个/最后一个渠道

通过追加列表以极向聚合

Discord.py -

以异步方式填充Pandas 数据帧

判断Python操作:如何从字面上得到所有decorator ?

按条件添加小计列

极点替换值大于组内另一个极点数据帧的最大值

将数字数组添加到Pandas DataFrame的单元格依赖于初始化

如何删除剪裁圆的对角线的外部部分

大Pandas 中的群体交叉融合

将索引表转换为Numy数组

PYODBC错误(SQL包含-26272个参数标记,但提供了235872个参数,HY 000)

Django-修改后的管理表单返回对象而不是文本

`Convert_time_zone`函数用于根据为极点中的每一行指定的时区检索值