【问题标题】:Excessive memory usage when creating one SQL database from another从另一个 SQL 数据库创建一个 SQL 数据库时内存使用过多
【发布时间】:2019-06-04 16:24:30
【问题描述】:

我正在尝试遍历一个 SQLite 数据库(我将其称为数据库 A),从其中的数据创建一些新变量,然后将这些新数据写入新的 SQLite 数据库(数据库 B)。

数据库 A 由表组成,这些表由有关特定月份的特定术语的推文组成(每条推文及其元数据都是一行,包括该月的每一天)。每个表的大小大约为 0.5 GB。

所以,我正在遍历这些表,创建一个变量,然后将这些新数据写入/提交到数据库 B。

问题是,在遍历几个表之后,我正在使用的服务器上的工作内存(我有 16 GB 的 RAM)完全用完了(使用 BASH 中的free -m 命令,我可以看到大约一半'buff / cache'正在使用RAM)。这不会产生任何我可以在输出文件中看到的错误(通常显示 Python 错误消息),但脚本会停止运行。

我认为这是由 SQLite (https://www.sqlite.org/tempfiles.html) 创建的临时文件的结果,随着 for 循环的继续,这些临时文件会继续增长。因此,我尝试逐日迭代表中的行,并在每天之后将新数据提交到数据库 B,以便删除回滚日志(见上面的链接)——这些临时 SQL 文件之一——被删除(从而释放内存)。但是,即使进行了这些更改,我也遇到了同样的问题(脚本停止)。

我不确定这里有多少代码会有所帮助,但这里是我正在做的事情的基本概述:

import sqlite3
import pandas

#this defines the SQL query; [long list of columns] is just comma separated column names: id, date, time, etc.
sql_query = ("SELECT DISTINCT [long list of columns] "
            "FROM term "
            "WHERE date = 'day';")

### HERE I GET ALL TABLES IN DATABASE A ###

#go through all the tables in Database A
for t in tables:

   term = t

   ### HERE I GET THE DAYS IN THE CURRENT TABLE ###

   #go through each day in the current table in Database A
   for day in days:

      #open the databases
      connection = sqlite3.connect("../SQL_database/Database_A.db3")
      lite_cursor = connection.cursor()
      connection_new = sqlite3.connect("../SQL_database/Database_B.db3")
      lite_cursor_new = connection_new.cursor()

      #change SQL query to match current day and term
      sql_query = sql_query.replace('day', day)

      #extract the data from the database and put it in the new database
      for chunk in pandas.read_sql_query(sql_query, connection, chunksize = 10000):

         ### HERE I PROCESS THE DATA ###

         #add the current data set to Database B
         new_table = term
         chunk.to_sql(new_table, connection_new, if_exists='append', index=True)

         #redefine SQL query; [long list of columns] is just comma separated column names: id, date, time, etc.
         sql_query = ("SELECT DISTINCT [long list of columns] "
                     "FROM term "
                     "WHERE date = 'day';")

         #commit the changes
         connection_new.commit()

         #close the databases
         connection.close()
         connection_new.close()  

当然,我想要的是让脚本运行时不会暂停/崩溃!有什么方法可以清除 SQLite 内存缓存,以便在 for 循环继续时 RAM 不会被吃掉?我以为 commit() 会释放一些内存,但显然它释放的不够。

提前谢谢你!

【问题讨论】:

  • 我对 SQLite 比 Pandas 更有信心节省内存。我有很多次成功处理的表比我的可用内存大,但我当然没有尝试同时将所有内容加载到内存中(熊猫所做的)。
  • 您好,感谢您的参与。我也想过同样的事情,这就是为什么我使用pandas.read_sql_query() 在表格中按天查询的原因,但也许即使这样仍然会读取整个表格(由一个月中的所有天组成)。尽管如此,奇怪的是脚本在遍历几个表后失败(不是在第一个表之后),这表明问题不是用 pandas 读取表,而是一些缓存系统继续增长尺寸。不过,也许我错过了什么!
  • 我看到您没有关闭光标。他们有单独的资源分配。也许连接没有跟踪它的游标,因此无法清理它们。所以尝试关闭它们。看看这是否有什么不同。
  • 我刚刚再次查看了您的代码。有些事情让我担心:您反复关闭并重新打开数据库连接。 AFAIK,如果在数据库完全释放所有内容之前关闭返回,它无用甚至可能有害,因为它可能在下次打开时出现问题。这可能是发生的事情,因为我无法想象你的代码中的什么会吃掉这么多内存。
  • buff/缓存?别担心。它实际上与空闲内存相同。操作系统尝试使用任何可用内存来缓存文件系统以提高性能。也就是说,从内存缓存读取比从磁盘读取要快。如果任何进程需要比“空闲”更多的内存,则释放缓冲区/缓存中的内存以为进程让路。 buff/cache 使用大量内存仅意味着您在许多或大文件上执行大量 I/O —— 您就是这样。

标签: python pandas sqlite


【解决方案1】:

我会尝试直接在 sqlite 级别执行此操作。

Sqlite 能够将附加数据库附加到当前连接,从而可以轻松地在不同数据库之间复制表。由于您没有添加大量处理,因此 pandas 相当无用,ATTACH DATABASE 应该足够了:

import sqlite3

#this defines the SQL query; [long list of columns] is just comma separated column names: id, date, time, etc.
sql_query = ("SELECT DISTINCT [long list of columns] "
            "FROM term "
            "WHERE date = 'day';")

#open the databases
connection = sqlite3.connect("../SQL_database/Database_A.db3")
connection.execute("ATTACH DATABASE '../SQL_database/Database_B.db3' as db_B")

### HERE I GET ALL TABLES IN DATABASE A ###

#go through all the tables in Database A
for t in tables:

   term = t

   ### HERE I GET THE DAYS IN THE CURRENT TABLE ###

   #go through each day in the current table in Database A
   for day in days:

      #change SQL query to match current day and term
      # but don't change original query because we'll need it on next iteration
      sql_query2 = sql_query.replace('day', day) 
      sql_query2 = sql_query2.replace('term', term) 

      # print(sql_query2, end=' ')           # uncomment to make sure of what happens

      # copy table values
      try:
          connection.execute("INSERT INTO db_B.{} ".format(term) + sql_query2)
          # print('inserted')                    # uncomment for traces
      except sqlite3.OperationalError:  # table does not exists
          connection.rollback()
          connection.execute("CREATE TABLE db_B.{} AS ".format(term) + sql_query2)
          # print('created')                     # uncomment for traces

   connection.commit()

connection.close()

这里唯一可能消耗资源的操作是SELECT DISTINCT,它需要扫描整个表以仅保留给定日期的不同行,但每次提交时都应释放资源。这可能需要一些时间,具体取决于表的数量和大小,但它不会崩溃。


对此答案的添加较晚,但我刚刚意识到有许多使用DISTINCT 关键字和WHERE date = 的请求。索引可以大大提高数据库性能。这里在提取信息之前添加索引会对时间和内存产生重大影响:

...
for t in tables:

   term = t

   connection.execute("CREATE INDEX IF NOT EXISTS I{0} ON {0}(date)"
                      .format(term))

   ### HERE I GET THE DAYS IN THE CURRENT TABLE ###
...

【讨论】:

  • 再次感谢您对此提供的所有帮助!我开始监视正在使用的内存,并在提取之前添加索引导致内存使用量大幅减少。
猜你喜欢
  • 2010-09-06
  • 2017-03-15
  • 2012-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-11
  • 1970-01-01
相关资源
最近更新 更多