【问题标题】:Improve pandas' to_sql() performance with SQL Server使用 SQL Server 提高 pandas 的 to_sql() 性能
【发布时间】:2020-11-20 01:45:02
【问题描述】:

我来找你是因为我无法解决 pandas.DataFrame.to_sql() 方法的问题。

我已经在我的脚本和我的数据库之间建立了连接,我可以发送查询,但实际上它对我来说太慢了。

我想找到一种方法来提高我的脚本在这方面的性能。也许有人会找到解决方案?

这是我的代码:

  engine = sqlalchemy.create_engine(con['sql']['connexion_string'])
  conn = engine.connect()
  metadata = sqlalchemy.Metadata()
  try : 
    if(con['sql']['strategy'] == 'NEW'): 
      query = sqlalchemy.Table(con['sql']['table'],metadata).delete()
      conn.execute(query)
      Sql_to_deploy.to_sql(con['sql']['table'],engine,if_exists='append',index = False,chunksize = 1000,method = 'multi')
    elif(con['sql']['strategy'] == 'APPEND'):
      Sql_to_deploy.to_sql(con['sql']['table'],engine,if_exists='append',index = False,chunksize = 1000,method = 'multi')
    else:
      pass
  except Exception as e:
    print(type(e))

当我退出 chunksize 和方法参数时,它正在工作并且太慢,这是它太慢的时刻(30000 行几乎 3 分钟)。当我输入这些参数时,我得到一个sqlalchemy.exc.ProgrammingError...

感谢您的帮助!

【问题讨论】:

  • 您使用的是哪个数据库后端?
  • 你使用的是mssql/pyodbc吗?
  • 您可能想在codereview.stackexchange.com 上发布此内容,它更关注此类请求
  • 我使用的是 SQL Server,用 mssql+pyodbc 作为 windows authetification。
  • 您在使用“ODBC Driver 17 for SQL Server”吗?

标签: python sql-server pandas sqlalchemy pyodbc


【解决方案1】:

我合成了一个包含 36k 行的数据帧。这总是在 select,带有昂贵的where 子句和一个group by,随着表的增长,它会稍微变慢,但总是

  1. 没有索引,因此插入速度很快
  2. 没有帮助选择的索引
  3. 在我笔记本电脑的 docker 容器内的 mariadb 上运行。所以并不是所有的都优化了
  4. 表现良好的所有默认值

更多信息

  1. 您的表上有哪些索引?经验法则越少越好。更多索引,更慢的插入
  2. 您从这个综合案例中看到了什么时机?
import numpy as np
import pandas as pd
import random, time
import sqlalchemy

a = np.array(np.meshgrid([2018,2019,2020], [1,2,3,4,5,6,7,8,9,10,11,12], 
                     [f"Stock {i+1}" for i in range(1000)],
                    )).reshape(3,-1)
a = [a[0], a[1], a[2], [round(random.uniform(-1,2.5),1) for e in a[0]]]
df1= pd.DataFrame({"Year":a[0], "Month":a[1], "Stock":a[2], "Sharpe":a[3], })


temptable = "tempx"

engine = sqlalchemy.create_engine('mysql+pymysql://sniffer:sniffer@127.0.0.1/sniffer')
conn = engine.connect()
try:
#     conn.execute(f"drop table {temptable}")
    pass
except sqlalchemy.exc.OperationalError:
    pass # ignore drop error if table does not exist
start = time.time()
df1.to_sql(name=temptable,con=engine, index=False, if_exists='append')
curr = conn.execute(f"select count(*) as c from {temptable}")
res = [{curr.keys()[i]:v for i,v in enumerate(t)} for t in curr.fetchall()]
print(f"Time: {time.time()-start:.2f}s database count:{res[0]['c']}, dataframe count:{len(df1)}")
curr.close()
start = time.time()
curr = conn.execute(f"""select Year, count(*) as c 
                        from {temptable} 
                        where Month=1 
                        and Sharpe between 1 and 2 
                        and stock like '%%2%%'
                        group by Year""")
res = [{curr.keys()[i]:v for i,v in enumerate(t)} for t in curr.fetchall()]
print(f"Time: {time.time()-start:.2f}s database result:{res} {curr.keys()}")
curr.close()
conn.close()

输出

Time: 1.23s database count:360000, dataframe count:36000
Time: 0.27s database result:[{'Year': '2018', 'c': 839}, {'Year': '2019', 'c': 853}, {'Year': '2020', 'c': 882}] ['Year', 'c']

【讨论】:

  • 实际上我的 sql 上没有索引,因为我将参数设置为 false。而在我的数据库上,我也没有索引可以检查。显然,我必须将我的数据放在表格中。但我的数据是整数和字符串。我的数据库中有一个日期时间列,也许是这个?但是对于很少的数据,它是有效的。我不知道为什么它不适用于大...
  • 我做的例子是字符串和浮点数。我也向它添加了一个日期时间列,并且没有更改时间(如预期的那样)。您正在运行什么 DBMS,它在 WAN 上是否远离?
  • 广域网是什么意思?
  • WAN = 广域网,LAN = 局域网。您的 python pandas 与您的 DBMS 的分离程度如何? ODBC 接口通常chatty,对于设计您的基础设施设置非常重要,这样中间层数据管理功能就“接近”您的 DBMS。多层应用程序设计的重点
【解决方案2】:

对于mssql+pyodbc,如果您这样做,您将从to_sql 获得最佳性能

  1. 使用 Microsoft 的 ODBC Driver for SQL Server,并且
  2. 在您的create_engine 呼叫中启用fast_executemany=True

例如,这段代码在我的网络上运行只需 3 秒多一点:

from time import time
import pandas as pd
import sqlalchemy as sa

ngn_local = sa.create_engine("mssql+pyodbc://mssqlLocal64")
ngn_remote = sa.create_engine(
    (
        "mssql+pyodbc://sa:_whatever_@192.168.0.199/mydb"
        "?driver=ODBC+Driver+17+for+SQL+Server"
    ),
    fast_executemany=True,
)

df = pd.read_sql_query(
    "SELECT * FROM MillionRows WHERE ID <= 30000", ngn_local
)

t0 = time()
df.to_sql("pd_test", ngn_remote, index=False, if_exists="replace")
print(f"{time() - t0} seconds")

fast_executemany=False(这是默认值)相同的过程需要 143 秒(2.4 分钟)。

【讨论】:

  • 非常感谢,我已经将 fastexecutemany 录制到 fast_executemany 上...
  • 确实,对于 50k 行,从 300 秒到 14 秒,显然更好!谢谢:D
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-14
  • 2011-03-03
  • 1970-01-01
相关资源
最近更新 更多