Python 在大约150个表之后,无论输入多少条记录,向PostgreSQL发送数据帧的速度都会减慢

Python 在大约150个表之后,无论输入多少条记录,向PostgreSQL发送数据帧的速度都会减慢,python,pandas,postgresql,sqlalchemy,psycopg2,Python,Pandas,Postgresql,Sqlalchemy,Psycopg2,背景:一个python工具,用于存储和管理每日股票价格。数据存储在本地托管的postgres数据库中。有一个主表,其中包含所有股票的列表,每个股票都有自己的定价数据表,当前编号约为500,这些数据是主表的外键 有一个python函数,它提取股票名称/符号的列表,并将它们存储在主表中,这里没有显示为真正不相关。第二个函数,从该表中检索股票名称列表,然后使用pandas webreader迭代调用定价数据并将其存储在数据库中 底层ORM是SQLalchemy,Psycopg2用作数据库适配器 问题:

背景:一个python工具,用于存储和管理每日股票价格。数据存储在本地托管的postgres数据库中。有一个主表,其中包含所有股票的列表,每个股票都有自己的定价数据表,当前编号约为500,这些数据是主表的外键

有一个python函数,它提取股票名称/符号的列表,并将它们存储在主表中,这里没有显示为真正不相关。第二个函数,从该表中检索股票名称列表,然后使用pandas webreader迭代调用定价数据并将其存储在数据库中

底层ORM是SQLalchemy,Psycopg2用作数据库适配器

问题:在函数运行过程中,数据库性能似乎会下降。它一开始很强大,在大约前150个插入中保持了这种性能。然而,在那之后,它的速度会减慢到爬行速度,并下降到大约1次插入/秒,可能会稍微慢一点。我附上了pgadmin仪表板的屏幕截图,您可以清楚地看到它只是在一段时间后下降

这种表现的下降似乎不受记录数量的影响,例如,如果我只提取每只股票1天的价格数据,则表现似乎与提取50天的数据相同。如果我停止脚本并重新启动它,性能会再次强劲启动,但一段时间后会像以前一样下降

尝试:我尝试了很多方法来解决这个问题。我也尝试过使用pandas to_sql函数。我还尝试将数据帧输出到CSV,然后使用psycopg2的copy_from函数将记录插入到每个表中。我已经通过下面的代码进行了更新,不再使用iter函数,并根据建议使用列表理解,但在我看来,主要原因仍然是在许多表上插入记录

问题:由于我不太确定是什么导致了这种行为,我在下面就潜在问题提出了一些想法

使用单独的表不是一个有用的或可扩展的解决方案,所有价格数据最好放在一个表中

我可能没有充分利用sqlalchemy核心API,过于依赖ORM方面/没有正确实现会话

有没有比Pangres/pd.to\u sql更好/更有效的方法来插入记录?请注意,复制CSV的性能没有任何改善


您的代码在一个大事务中执行所有这些插入操作,这可能会因为该事务的管理开销增加而陷入困境吗?@GordThompson,因此,似乎每个插入操作只有一个事务。我打开了sqlalchemy的echo并获得以下内容:INFO sqlalchemy.engine.base.engine SELECT*FROM stockschema.aapl ORDER BY Date DESC LIMIT 1 INFO sqlalchemy.engine.base.engine SELECT relname FROM pg_class c join pg_namespace n on n.oid=c.relnamespace其中n.nspname=%schemas和relname=%name INFO sqlalchemy.engine.base.engine{'schema':'stockschema','name':'aapl'}insert语句-不是为了简洁而复制的。INFO sqlalchemy.engine.base.engine COMMITOkay,很高兴知道。至于第2点,看起来你没有真正做任何ORM工作,所以你可以跳过使用会话,只使用引擎。begin As conn:为了降低sqlalchemy开销。真棒,感谢INFO@GordThompson,这不是我的or的一部分原始代码,但我想尝试一下,看看是否真的对性能有影响。不幸的是,两种方法都没有改进。是什么让你认为数据库是一个限制?也许雅虎检测到你正在删除它们并限制你。循环所有股票,将每个股票保存到它的CSV文件中。然后循环每个CSV文件,加载em到数据库。这应该澄清瓶颈在哪里。您的代码是否在一个大事务中执行所有这些插入,而随着该事务的管理开销增加,可能会使事情陷入困境?@GordThompson,因此,似乎每个插入都有一个事务。我打开了sqlalchemy的echo,得到了以下结果lowing:INFO sqlalchemy.engine.base.engine SELECT*FROM stockschema.aapl ORDER BY Date DESC限制1 INFO sqlalchemy.engine.base.engine SELECT relname FROM pg_class c join pg_namespace n on n.oid=c.relnamespace其中n.nspname=%schemas和relname=%name INFO sqlalchemy.engine.base.engine{'schema':'stockschema','name':'aapl'}insert语句-不是为了简洁而复制的。INFO sqlalchemy.engine.base.engine COMMITOkay,很高兴知道。至于第2点,看起来你没有真正做任何ORM工作,所以你可以跳过使用会话,只使用引擎。begin As conn:为了降低sqlalchemy开销。真棒,感谢INFO@GordThompson,这不是我的or的一部分原始代码,但我想试试
我不想看看是否真的对性能产生了影响。不幸的是,这两种方法都没有改进。是什么让你认为数据库是一个限制?也许雅虎检测到你在刮它们,并扼杀了你。循环所有库存,将每个库存保存到其CSV文件中。然后循环每个CSV文件,将它们加载到数据库中。这应该澄清瓶颈在哪里。
# Database basics.
engine = sqla.create_engine(f'postgresql+psycopg2://{dbuser}:{dbpass}@{dbhost}/{dbname}')
conn = engine.connect()
Session = sessionmaker(engine, expire_on_commit=True)
session = Session()

# The main function.
def insertData():
    df = pd.read_sql_table('stock', schema=schema_name, con=conn) # Read the names of all stocks in the master table.
    for stock_name, stock_id in zip(df['Stock_symbol'], df['Stock_id']): # Use list comprehension as faster than iterrtuples. Get the stock id and stock symbol for each row in the data frame.
        read_price_data = pd.read_sql_query(sql=f"SELECT * FROM stockschema.\"{stock_name}\" ORDER BY \"Date\" DESC LIMIT 1", con=conn, parse_dates={'Date'}) # Read the most recent price data (if any) for each stock symbol and obtain the date.
        with session.begin(subtransactions=True): # Opern a session. Removing this doesn't appear to impact performance in anyways, looking at the echo output from psycopg2, it seems like pangres commits every transaction anyways.
            start, end = dt.datetime(2020, 12, 12), prev_weekday(dt.date.today()) # Set start and end dates to fetch data. Setting this to even 1 day of records is just as slow as 20.
            price_data = web.DataReader(stock_name, 'yahoo', start, end) # Fetch the data frame from Yahoo.
            price_data['Volume'] = price_data['Volume'].astype(int) # Some of the volume data has decimal (e.g 123.0). Removing these to match-up with DB column type.
            count = len(price_data.index) # Some data manipulation to just ensure that the Foreign key - Stock id is captured in each df before being written to the db.
            sid = [stock_id] * count # As above
            price_data.insert(0, 'Stock_id', sid) # Inserting the above new column into the df.
            pangres.upsert(engine=engine, df=price_data, table_name=stock_name, if_row_exists='ignore', schema=schema_name, create_schema=False, add_new_columns=False, adapt_dtype_of_empty_db_columns=False) # Upserting the dataframe into the relevant stcck's table. Utilising df_to_sql, and psycopg2's copy_from also do not provide any benefits performance wise.