循环查询数据的性能问题及优化
糟糕的代码,对代码维护、性能、团队协作都会造成负面影响,所以,先设计再实现,谋而后动。
1. 使用IN查询替换for循环
优化前代码(简化版,以MySQL为例):
sql = 'SELECT A.real_name, A.phone, A.gender FROM tb_user AS A INNER JOIN tb_trade AS B on A.id=B.user_id WHERE B.id=%s;'
for id in trade_ids:
user = db_mysql.find(sql, [id])
# TODO: do some work
这个代码的本意是要查询每个交易的购买用户的信息,和其他信息拼接起来,然后写入EXCEL文件。这种写法,简单明了,只要按照逻辑来写就好了,然而随着交易越来越多,这段代码会跑的越来越慢,即使建立了索引,但是却无法避免每次的数据库访问开销。
优化后代码:
sql = 'SELECT A.real_name, A.phone, A.gender, B.id FROM tb_user AS A INNER JOIN tb_trade AS B on A.id=B.user_id WHERE B.id IN (%s);'
place_holders = ','.join(map(lambda x: '%s', id_list))
users = db_mysql.findAll(sql % place_holders, [trade_ids])
for id in users:
# TODO: do some work
使用IN替换掉for循环,一次查询拿到所有的数据,然后在for循环中取处理业务逻辑。该方法在MySQL与Mongo中均可以使用,只是语法不同而已。
2. 使用聚合查询替换for循环
优化前代码(简化版,以MongoDB为例):
avaliable_companies = []
condition = {
'is_active': True,
'create_time': {'$lt': datatime.now()},
'suspended': False
}
for company in companies:
condition['company'] = company['_id']
job = db_mongo.job.find_one(condition)
if job:
avaliable_companies.append(job)
count = len(avaliable_companies)
这段代码的本意是要查询截止到当前时间,生成的job记录是来自哪几家company。同样的,随着数据量的增加,这段代码会跑的越来越慢。
pipeline = [
{'$match': {
'is_active': True,
'create_time': {'$lt': datatime.now()},
'suspended': False,
'company': {'$in': map(lambda x: x['_id'], companies)}
}},
{'$group': {'_id': 'company'}}
]
agg_result = db_mongo.job.aggregate(pipeline)
count = len(list(agg_result))
使用聚合可以一次查询出结果,当然,这里也可以通过IN查询来做,同样可以提高性能。
3. 使用pipeline来查询redis
Redis通常用来做数据缓存,降低数据库的命中率,从而提供并发性能。然而,如果使用不当,你会发现虽然使用了缓存,但是时间查询效率并没特别大的提升。
优化前代码(简化版):
redis_cli = get_redis()
for id in user_ids:
result = redis_cli.get('user_last_active_time:%d' % id)
这个代码本意是要查询一组用户的最近一次活跃时间,这些活跃时间都缓存在Redis中,但是这个代码,如果user_ids的列表很长,就会发现这个缓存查询很慢,因为每次访问redis都需要建立一次IO请求。
优化后代码:
redis_cli = get_redis()
pipeline = redis_cli.pipeline(transaction=False)
for d in user_ids:
pipeline.get('user_last_active_time:%d' % id)
active_time_list = pipeline.execute()
使用Redis的pipeline来一次获取所有的数据,这么做会比上面的快几十倍,在数据量大的情况下。
上面通过三个实例来阐述循环查询对性能的影响和优化的方法,写这篇博客的目的并不仅仅要介绍这些技巧方法,因为技巧方法远不止这些,而是想借此传达一个观点:编程,应该设计先于写代码。虽然都是实现同样的逻辑功能,但是如果没有进行一番设计和思考,必然会写出一些糟糕的代码,其会对代码维护、性能、团队协作都会造成负面影响。
文章不错?点个【在看】吧! 👇