天天看點

sql for循環_循環查詢資料的性能問題及優化

sql for循環_循環查詢資料的性能問題及優化

大資料技術與架構 點選右側關注,大資料開發領域最強公衆号!

sql for循環_循環查詢資料的性能問題及優化
sql for循環_循環查詢資料的性能問題及優化

暴走大資料 點選右側關注,暴走大資料!

sql for循環_循環查詢資料的性能問題及優化

糟糕的代碼,對代碼維護、性能、團隊協作都會造成負面影響,是以,先設計再實作,謀而後動。

這裡的循環查詢,指的是在一個for循環中,不斷通路資料庫來查詢資料。在剛接手公司資料報表系統時,發現有很多每日報告跑的出奇的慢,通過一番診斷後,發現主要來自兩個方面的因素:一是需要對資料庫的某些字段建立和優化索引,二是存在了很多糟糕的代碼,這些代碼在一個循環中不斷的通路資料庫,查詢資料。 本文将摘取其中的三個例子來說明如何避免循環查詢帶來的性能問題,涉及常用的三種資料存儲:MySQL,MongoDB和Redis。

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來一次擷取所有的資料,這麼做會比上面的快幾十倍,在資料量大的情況下。

上面通過三個執行個體來闡述循環查詢對性能的影響和優化的方法,寫這篇部落格的目的并不僅僅要介紹這些技巧方法,因為技巧方法遠不止這些,而是想借此傳達一個觀點:程式設計,應該設計先于寫代碼。雖然都是實作同樣的邏輯功能,但是如果沒有進行一番設計和思考,必然會寫出一些糟糕的代碼,其會對代碼維護、性能、團隊協作都會造成負面影響。

歡迎點贊+收藏+轉發朋友圈素質三連

sql for循環_循環查詢資料的性能問題及優化

文章不錯?點個【在看】吧! ?