Flask+SQLAlchemy中如何优化循环内带可变参数的函数调用及并发更新
一、数据库操作优化(最立竿见影的改动)
原代码的核心耗时点之一是循环内频繁的数据库查询和事务提交,先从这里入手:
避免重复查询Location对象
原update_all_location_data已经通过查询拿到了locations列表,完全可以直接把location对象传给update_location_data,不用再调用session.get(Location, location_id)重复查询数据库,这能节省大量DB往返时间。批量提交事务,而非每次循环提交
原代码每次更新一个位置就调用session.commit(),数据库事务的提交开销很高,把提交操作移到循环结束后(或分批次提交),能大幅减少事务次数。移除不必要的
session.add(location)
从SQLAlchemy session中查询出来的对象默认处于“托管”状态,修改后会自动被session追踪,不需要手动add。
优化后的数据库操作代码示例:
def update_location_data(location, data): # 直接使用传入的location对象,无需重复查询 ... for index in (0, x): location.data[index] = new_data flag_modified(location, "data") # 移除session.add和session.commit,交给外层统一处理 def update_all_location_data(user_id): locations = get_locations_by_user_id(user_id) # 假设这是获取locations的方法 # 先处理所有更新逻辑,最后统一提交 for location in locations: lat = location.lat long = location.long params = {"lat": lat, "lon": long, ...} data = requests.get(URL, params=params).json() update_location_data(location, data) # 所有更新完成后统一提交 session.commit()
如果处理的位置数量极大(比如上千条),可以分批次提交,避免内存占用过高:
batch_size = 50 for idx, location in enumerate(locations): # ... 数据更新逻辑 ... if (idx + 1) % batch_size == 0: session.commit() session.flush() # 清空session缓存 # 提交剩余数据 session.commit()
二、API请求并发优化(解决IO等待瓶颈)
原代码是同步串行请求外部API,这是另一个核心耗时点——大部分时间都在等待API响应。针对IO密集型任务,用并发请求可以把总耗时从“N个请求的时间总和”降到“单个请求的最长耗时”。
方案1:使用线程池(简单易集成)
用concurrent.futures.ThreadPoolExecutor实现多线程请求,适合不需要大规模并发的场景:
from concurrent.futures import ThreadPoolExecutor import requests def fetch_location_data(location): params = {"lat": location.lat, "lon": location.long, ...} response = requests.get(URL, params=params) return (location, response.json()) def update_all_location_data(user_id): locations = get_locations_by_user_id(user_id) # 并发请求API,线程数根据API限制调整(比如设为10) with ThreadPoolExecutor(max_workers=10) as executor: results = list(executor.map(fetch_location_data, locations)) # 批量更新数据库 for location, data in results: update_location_data(location, data) session.commit()
方案2:使用异步请求(更高性能)
如果并发量较大,推荐用aiohttp实现异步请求,配合异步SQLAlchemy(async-sqlalchemy)进一步提升效率(注意Flask应用工厂模式下需要适配异步上下文):
import aiohttp import asyncio async def fetch_location_data_async(session, location): params = {"lat": location.lat, "lon": location.long, ...} async with session.get(URL, params=params) as response: return (location, await response.json()) async def update_all_location_data_async(user_id): locations = get_locations_by_user_id(user_id) # 注意如果是异步查询,要改成await async with aiohttp.ClientSession() as aio_session: tasks = [fetch_location_data_async(aio_session, loc) for loc in locations] results = await asyncio.gather(*tasks) # 异步更新数据库(需使用async-sqlalchemy的session) async with async_session() as session: for location, data in results: update_location_data(location, data) await session.commit()
三、其他细节优化
- API请求缓存:如果同一位置的API数据更新频率不高,可以添加缓存(比如用Redis),避免重复请求相同坐标的数据。
- 增量更新:只更新真正有变化的数据,比如对比API返回的新数据和数据库中旧数据,只有当差异存在时才修改
location.data,减少不必要的数据库写入。 - 数据库索引优化:确保
tracked表的user_id字段有索引,提升get_locations_by_user_id的查询速度。
内容的提问来源于stack exchange,提问作者joepaji

