2012-01-03 3 views
5

データベース内のすべての行に対してバッチ処理を実行しています。これは、すべての単一のモデルを選択し、それに対して何かをすることを含む。これをチャンクに分割し、チャンクでチャンクします。Django QuerySetを使用してデータベースをチャンクで処理する最も良い方法は?

私は現在Paginatorを使用しています。便利です。これは、順番にページできるように、値の順序付けが必要であることを意味します。これはorderlimit句を持つSQL文を生成し、各チャンクについてはPostgresがテーブル全体をソートしている可能性があると思います(ただし、内部についての知識はありません)。私が知っていることは、データベースが約50%のCPUにあるということです。それはちょうどselectを実行するには高すぎると思います。

RDMBS/CPUに優しい方法でテーブル全体を反復処理する最良の方法は何ですか?

データベースの内容がバッチ操作中に変更されないと仮定します。

答えて

5

説明から実際に処理する行ののソート順を気にする必要はありません。あなたは(!私は期待して)あなたのテーブルの主キーを持っている場合は、分割のこの粗製の方法は、はるかに高速次のようになります。

SELECT * FROM tbl WHERE id BETWEEN 0 AND 1000; 
SELECT * FROM tbl WHERE id BETWEEN 1001 AND 2000; 
... 

これは、任意のオフセットと(ほぼ)同じ任意のサイズのために同じを行い、テーブルの。 は、それに応じて、主キーとパーティションの最小値と最大値を取得します。とは対照的に、

SELECT min(id), max(id) from tbl; -- then divide in suitable chunks 

を:

SELECT * FROM tbl ORDER BY id LIMIT 1000; 
SELECT * FROM tbl ORDER BY id LIMIT 1000 OFFSET 1000; 
... 

すべての行をソートする必要があると性能がでさらに低下するので、これは一般的に遅いですより高いオフセットと大きなテーブル。次のように任意のDjangoクエリセットのためにこれを行うであろう

効用関数である:

+0

これはレコードが 'sort'句を使用せずに同じ順序で返されることを想定しています。これは正しいです?また、私が 'Meta'クラスにデフォルトのソートをしていれば、何とかそれをクエリ用に削除できますか? – Joe

+0

@ Joe:基本的に同じレコードを取得しますが、ソートされません。あなたのID空間に隙間がある場合、返されるレコードの数は、すべての呼び出しで予想よりも少なくなる可能性があります。 LIMIT/OFFSETではソートされた行数が固定数になります(テーブルごとの最後の呼び出しを除く)。私は 'Meta'クラスを扱う方法はありませんが、LIMIT/OFFSETの行を並べ替える必要があります。 –

+0

アーウィン、本当に申し訳ありませんが、私はあなたの答えを正しく読まなかった。これがより速いと確信していますか? 'between'節はIDが既にソートされている場合、または毎回全テーブルスキャンを実行する場合にのみ確実に機能します。 – Joe

2

次のコードは、Djangoのクエリセットのために(BETWEEN使用)上記アーウィンの答えを実現します。デフォルトでは、 'id'はbetween句に使用するのに適したフィールドであると仮定します。

def chunked_queryset(qs, batch_size, index='id'): 
    """ 
    Yields a queryset split into batches of maximum size 'batch_size'. 
    Any ordering on the queryset is discarded. 
    """ 
    qs = qs.order_by() # clear ordering 
    min_max = qs.aggregate(min=models.Min(index), max=models.Max(index)) 
    min_id, max_id = min_max['min'], min_max['max'] 
    for i in range(min_id, max_id + 1, batch_size): 
     filter_args = {'{0}__range'.format(index): (i, i + batch_size - 1)} 
     yield qs.filter(**filter_args) 

このように使用されるでしょう:

for chunk in chunked_queryset(SomeModel.objects.all(), 20): 
    # `chunk` is a queryset 
    for item in chunk: 
     # `item` is a SomeModel instance 
     pass 
関連する問題