2012-04-23 26 views
7

Tôi đang cố gắng tối ưu hóa truy vấn cơ sở dữ liệu cho một ứng dụng Django. Dưới đây là một ví dụ đơn giản:trường nhiều người django: chỉ tìm nạp trước các khóa chính

class Label(models.Model): 
    name = models.CharField(max_length=200) 
    # ... many other fields ... 

class Thing(models.Model): 
    name = models.CharField(max_length=200) 
    labels = models.ManyToManyField(Label) 

Tôi có một chức năng mà lấy về tất cả Label s và Thing s và đặt chúng vào một cấu trúc dữ liệu JSON, trong đó Thing s tham khảo Label s sử dụng id s của họ (khóa chính). Một cái gì đó như thế này:

{ 
    'labels': [ 
     { 'id': 123, 'name': 'label foo' }, 
     ... 
    ], 
    'things': [ 
     { 'id': 45, 'name': 'thing bar', 'labels': [ 123, ... ] }, 
     ... 
    ] 
} 

Cách hiệu quả nhất để có được cấu trúc dữ liệu như vậy bằng Django là gì? Giả sử tôi có LLabel s và TThing s, và tỷ lệ trung bình ThingxLabel s.

Phương pháp 1:

data = {} 
data['labels'] = [model_to_dict(label) for label in Label.objects.all()] 
data['things'] = [model_to_dict(thing) for thing in Thing.objects.all()] 

Điều này làm cho (1 + 1 + T) truy vấn cơ sở dữ liệu, vì model_to_dict(thing) nhu cầu để lấy Label s cho mỗi Thing riêng.

Cách 2:

data = {} 
data['labels'] = [model_to_dict(label) for label in Label.objects.all()] 
data['things'] = [model_to_dict(thing) for thing in 
        Thing.objects.prefetch_related('labels').all()] 

Điều này làm cho (1 + 1 + 1) cơ sở dữ liệu truy vấn mà thôi, kể từ khi Thing s lấy bây giờ có họ Label s tìm nạp trước trong một truy vấn bổ sung duy nhất.

Điều này vẫn không thỏa đáng.prefetch_related('labels') sẽ tìm nạp nhiều bản sao của cùng một Label, trong khi tôi chỉ cần id s của mình. Có cách nào để tìm nạp trước các số id s chỉ trong số Label không? Tôi đã thử prefetch_related('labels__id') nhưng điều đó không hiệu quả. Tôi cũng lo ngại rằng vì T lớn (hàng trăm), prefetch_related('labels') dẫn đến truy vấn SQL với mệnh đề IN lớn. L là nhỏ hơn nhiều (< 10), vì vậy tôi có thể làm điều này thay vì:

Phương pháp 3:

data = {} 
data['labels'] = [model_to_dict(label) for label in 
        Label.objects.prefetch_related('thing_set').all()] 
things = list(Thing.objects.all()) 
# plug in label ids by hand, and also fetch things that have zero labels 
# somehow 

Điều này dẫn đến một IN khoản nhỏ hơn, nhưng vẫn không thỏa đáng vì prefetch_related('thing_set') fetches trùng lặp Thing s, nếu Thing có nhiều Label s.

Tóm tắt:

LabelThing được nối với nhau bằng một ManyToManyField. Tôi đang tìm nạp tất cảLabel s và Thing s. Vậy làm thế nào để tôi cũng lấy mối quan hệ nhiều-nhiều của họ một cách hiệu quả?

+1

Có thể thử sử dụng mô hình trung gian cho m2m? Lược đồ DB và bất kỳ thứ gì khác sẽ vẫn giữ nguyên nhưng bạn sẽ chỉ có thể 'fetch_related' mô hình này và lấy ID của nhãn từ nó. Nếu bạn sẽ liên kết nó với đối số 'đến' với M2M, một số phương thức như' add() 'sẽ bị hỏng, nhưng bạn có thể cung cấp' db_table' theo cách thủ công cho nó và không chạm vào trường m2m, vì vậy nó sẽ hoạt động. – ilvar

+0

Cảm ơn @ilvar, nhận xét của bạn đã dẫn tôi đến câu trả lời bên dưới. – cberzan

Trả lời

7

Tôi hiểu rồi. Nhờ ilvar, người có nhận xét về câu hỏi đã chỉ tôi đến through tables.

Nếu bạn không chỉ định một rõ ràng thông qua mô hình, vẫn còn là một ngầm qua lớp mô hình mà bạn có thể sử dụng để truy cập trực tiếp bảng tạo để giữ hiệp hội. Nó có ba trường để liên kết các mô hình .

câu chuyện dài ngắn:

# Fetch all labels and things: 
labels = list(Label.objects.all()) 
things = list(Thing.objects.all()) 
# Fetch all label-thing pairs: 
labels_of = defaultdict(lambda: []) 
for pair in Thing.labels.through.objects.filter(label__in=labels): 
    labels_of[pair.thing_id].append(pair.label_id) 
# Put everything together: 
data = {} 
data['labels'] = [model_to_dict(label) for label in labels] 
data['things'] = [] 
for thing in things: 
    thing_dict = model_to_dict(thing, exclude='labels') 
    thing_dict['labels'] = labels_of[thing.id] 
    data['things'].append(thing_dict) 

Điều này làm cho (1 + 1 + 1) truy vấn, và không lấy bất cứ điều gì lặp đi lặp lại. Tôi cũng có thể thay đổi người đầu tiên cho vòng lặp để:

for pair in Thing.labels.through.objects.filter(thing__in=things): 

trong trường hợp tôi có nhiều Label s hơn Thing s, mà sẽ dẫn đến một truy vấn với một IN khoản nhỏ hơn.

Django-debug-toolbar 's debugsqlshell lệnh quản lý là tuyệt vời để thực sự thấy các truy vấn mà một đoạn mã đang tạo.

Các vấn đề liên quan