Câu hỏi Vấn đề truy vấn N + 1 SELECT là gì?


SELECT N + 1 thường được nói là một vấn đề trong các cuộc thảo luận ánh xạ đối tượng-quan hệ (ORM), và tôi hiểu rằng nó có liên quan đến việc phải tạo ra rất nhiều truy vấn cơ sở dữ liệu cho một thứ đơn giản trong thế giới đối tượng.

Có ai có một lời giải thích chi tiết hơn về vấn đề này không?


1299
2017-09-18 21:30


gốc


IMO javalobby.org/java/forums/t20533.html lời giải thích này tốt hơn - didxga
Đây là một liên kết tuyệt vời với lời giải thích tốt đẹp về sự hiểu biết n + 1 vấn đề. Nó cũng bao gồm các giải pháp để chống lại vấn đề này: architects.dzone.com/articles/how-identify-and-resilve-n1 - aces.
Có một số bài đăng hữu ích nói về vấn đề này và bản sửa lỗi có thể có. Các vấn đề thường gặp về ứng dụng và cách khắc phục chúng: Vấn đề chọn N + 1, Bullet (Silver) cho vấn đề N + 1, Tải chậm - tải nhanh - cateyes


Các câu trả lời:


Giả sử bạn có một bộ sưu tập Car đối tượng (hàng cơ sở dữ liệu) và mỗi Car có một bộ sưu tập Wheel đối tượng (cũng có hàng). Nói cách khác, Car -> Wheel là mối quan hệ 1 - nhiều.

Bây giờ, giả sử bạn cần phải lặp qua tất cả các ô tô và cho mỗi xe, in ra một danh sách các bánh xe. Việc triển khai O / R ngây thơ sẽ làm như sau:

SELECT * FROM Cars;

Và sau đó cho mỗi Car:

SELECT * FROM Wheel WHERE CarId = ?

Nói cách khác, bạn có một lựa chọn cho Ô tô, và sau đó chọn N bổ sung, trong đó N là tổng số xe ô tô.

Ngoài ra, người ta có thể nhận được tất cả các bánh xe và thực hiện tra cứu trong bộ nhớ:

SELECT * FROM Wheel

Điều này làm giảm số lượng chuyến đi khứ hồi đến cơ sở dữ liệu từ N + 1 đến 2. Hầu hết các công cụ ORM cung cấp cho bạn một số cách để ngăn chặn N + 1 lựa chọn.

Tài liệu tham khảo: Java Persistence với Hibernate, chương 13.


768
2017-09-18 21:36



Để làm rõ về "Điều này là xấu" - bạn có thể nhận được tất cả các bánh xe với 1 lựa chọn (SELECT * from Wheel;), thay vì N + 1. Với một N lớn, hiệu suất hit có thể rất đáng kể. - tucuxi
@tucuxi Tôi rất ngạc nhiên khi bạn nhận được rất nhiều upvotes vì ​​đã sai. Một cơ sở dữ liệu rất tốt về các chỉ mục, thực hiện truy vấn cho một CarID cụ thể sẽ trở lại rất nhanh. Nhưng nếu bạn có tất cả các bánh xe là một lần, bạn sẽ phải tìm kiếm CarID trong ứng dụng của bạn, mà không được lập chỉ mục, điều này là chậm hơn. Trừ khi bạn có các vấn đề về độ trễ lớn khi tiếp cận cơ sở dữ liệu của bạn, việc n + 1 thực sự nhanh hơn - và có, tôi đã đánh giá nó với một lượng lớn mã thế giới thực. - Ariel
@ariel Cách 'đúng' là để có được tất cả các các bánh xe, được yêu cầu bởi CarId (1 lựa chọn) và nếu cần thêm chi tiết hơn CarId, hãy thực hiện truy vấn thứ hai cho tất cả các ô tô (tổng cộng 2 truy vấn). In ấn mọi thứ ra là tối ưu, và không có chỉ mục hoặc lưu trữ thứ cấp được yêu cầu (bạn có thể lặp qua kết quả, không cần phải tải xuống tất cả). Bạn đã đánh giá sai điều. Nếu bạn vẫn tự tin về điểm chuẩn của mình, bạn có nhớ gửi một nhận xét dài hơn (hoặc câu trả lời đầy đủ) giải thích thử nghiệm và kết quả của bạn không? - tucuxi
"Hibernate (Tôi không quen thuộc với các khung công tác ORM khác) cung cấp cho bạn một số cách để xử lý nó." và cách này là gì? - Tima
@Ariel Hãy thử chạy điểm chuẩn của bạn với cơ sở dữ liệu và máy chủ ứng dụng trên các máy riêng biệt. Theo kinh nghiệm của tôi, các chuyến đi khứ hồi tới cơ sở dữ liệu tốn nhiều chi phí hơn so với chính truy vấn đó. Vì vậy, có, các truy vấn thực sự nhanh chóng, nhưng đó là các chuyến đi vòng mà tàn phá havok. Tôi đã chuyển đổi "WHERE Id = const"đến" WHERE Id IN (const, const, ...) "và nhận được đơn đặt hàng của cường độ tăng ra khỏi nó. - Hans


SELECT 
table1.*
, table2.*
INNER JOIN table2 ON table2.SomeFkId = table1.SomeId

Điều đó sẽ cho bạn một tập kết quả trong đó các hàng con trong bảng 2 gây ra sự trùng lặp bằng cách trả về kết quả table1 cho mỗi hàng con trong bảng2. Các nhà lập bản đồ O / R nên phân biệt các cá thể table1 dựa trên một trường khóa duy nhất, sau đó sử dụng tất cả các cột table2 để điền các cá thể con.

SELECT table1.*

SELECT table2.* WHERE SomeFkId = #

N + 1 là nơi truy vấn đầu tiên điền vào đối tượng chính và truy vấn thứ hai điền tất cả các đối tượng con cho mỗi đối tượng chính duy nhất được trả về.

Xem xét:

class House
{
    int Id { get; set; }
    string Address { get; set; }
    Person[] Inhabitants { get; set; }
}

class Person
{
    string Name { get; set; }
    int HouseId { get; set; }
}

và các bảng có cấu trúc tương tự. Một truy vấn duy nhất cho địa chỉ "22 Valley St" có thể trở lại:

Id Address      Name HouseId
1  22 Valley St Dave 1
1  22 Valley St John 1
1  22 Valley St Mike 1

O / RM phải điền vào một cá thể của Trang chủ có ID = 1, Địa chỉ = "22 Thung lũng St" và sau đó điền vào mảng Cư dân với các cá thể Người cho Dave, John và Mike chỉ với một truy vấn.

Truy vấn N + 1 cho cùng một địa chỉ được sử dụng ở trên sẽ dẫn đến:

Id Address
1  22 Valley St

với một truy vấn riêng biệt như

SELECT * FROM Person WHERE HouseId = 1

và tạo ra một tập dữ liệu riêng biệt như

Name    HouseId
Dave    1
John    1
Mike    1

và kết quả cuối cùng giống như trên với truy vấn đơn lẻ.

Những lợi thế để lựa chọn duy nhất là bạn nhận được tất cả các dữ liệu lên phía trước mà có thể là những gì bạn cuối cùng mong muốn. Ưu điểm của N + 1 là độ phức tạp của truy vấn bị giảm và bạn có thể sử dụng tải chậm khi các tập kết quả con chỉ được tải theo yêu cầu đầu tiên.


98
2017-09-18 21:43



Ưu điểm khác của n + 1 là nó nhanh hơn vì cơ sở dữ liệu có thể trả về kết quả trực tiếp từ một chỉ mục. Làm việc tham gia và sau đó phân loại yêu cầu một bảng tạm thời, đó là chậm hơn. Lý do duy nhất để tránh n + 1 là nếu bạn có nhiều thời gian chờ nói chuyện với cơ sở dữ liệu của bạn. - Ariel
Tham gia và sắp xếp có thể khá nhanh (vì bạn sẽ tham gia vào các trường được lập chỉ mục và có thể được sắp xếp). Làm thế nào lớn là 'n + 1' của bạn? Bạn có thực sự tin rằng vấn đề n + 1 chỉ áp dụng cho các kết nối cơ sở dữ liệu có độ trễ cao không? - tucuxi
@ariel - Lời khuyên của bạn rằng N + 1 là "nhanh nhất" là sai, mặc dù điểm chuẩn của bạn có thể đúng. Làm thế nào là có thể? Xem en.wikipedia.org/wiki/Anecdotal_evidence, và cũng bình luận của tôi trong câu trả lời khác cho câu hỏi này. - Lee
@Ariel - Tôi nghĩ rằng tôi hiểu nó tốt :). Tôi chỉ đang cố gắng chỉ ra rằng kết quả của bạn chỉ áp dụng cho một bộ điều kiện. Tôi có thể dễ dàng xây dựng một ví dụ phản đối cho thấy điều ngược lại. Điều đó có ý nghĩa? - Lee
Để nhắc lại, vấn đề SELECT N + 1 là, ở cốt lõi của nó: Tôi có 600 bản ghi để truy xuất. Có nhanh hơn để có được tất cả 600 trong số họ trong một truy vấn hoặc 1 tại một thời điểm trong 600 truy vấn. Trừ khi bạn đang sử dụng MyISAM và / hoặc bạn có một lược đồ được lập chỉ mục / chuẩn hóa kém (trong trường hợp đó ORM không phải là vấn đề), một db được điều chỉnh đúng sẽ trả về 600 hàng trong 2 ms, trong khi trả về các hàng riêng lẻ trong khoảng 1 ms mỗi. Vì vậy, chúng ta thường thấy N + 1 lấy hàng trăm mili giây trong đó sự tham gia chỉ mất một vài - Dogs


Nhà cung cấp có mối quan hệ một-nhiều với Sản phẩm. Một Nhà cung cấp có (nguồn cung cấp) nhiều Sản phẩm.

***** Table: Supplier *****
+-----+-------------------+
| ID  |       NAME        |
+-----+-------------------+
|  1  |  Supplier Name 1  |
|  2  |  Supplier Name 2  |
|  3  |  Supplier Name 3  |
|  4  |  Supplier Name 4  |
+-----+-------------------+

***** Table: Product *****
+-----+-----------+--------------------+-------+------------+
| ID  |   NAME    |     DESCRIPTION    | PRICE | SUPPLIERID |
+-----+-----------+--------------------+-------+------------+
|1    | Product 1 | Name for Product 1 |  2.0  |     1      |
|2    | Product 2 | Name for Product 2 | 22.0  |     1      |
|3    | Product 3 | Name for Product 3 | 30.0  |     2      |
|4    | Product 4 | Name for Product 4 |  7.0  |     3      |
+-----+-----------+--------------------+-------+------------+

Các yếu tố:

  • Chế độ lười cho Nhà cung cấp được đặt thành "true" (mặc định)

  • Chế độ tìm nạp được sử dụng để truy vấn trên Sản phẩm là Chọn

  • Chế độ tìm nạp (mặc định): Thông tin nhà cung cấp được truy cập

  • Bộ nhớ đệm không đóng vai trò lần đầu tiên

  • Nhà cung cấp được truy cập

Chế độ tìm nạp là Chọn Tìm nạp (mặc định)

// It takes Select fetch mode as a default
Query query = session.createQuery( "from Product p");
List list = query.list();
// Supplier is being accessed
displayProductsListWithSupplierName(results);

select ... various field names ... from PRODUCT
select ... various field names ... from SUPPLIER where SUPPLIER.id=?
select ... various field names ... from SUPPLIER where SUPPLIER.id=?
select ... various field names ... from SUPPLIER where SUPPLIER.id=?

Kết quả:

  • 1 câu lệnh chọn cho Sản phẩm
  • N chọn báo cáo cho nhà cung cấp

Đây là vấn đề chọn N + 1!


58
2017-12-01 13:35



Nó có phải là 1 lựa chọn cho các nhà cung cấp sau đó N chọn cho sản phẩm? - bencampbell_14


Tôi không thể bình luận trực tiếp trên các câu trả lời khác, bởi vì tôi không có đủ danh tiếng. Nhưng đáng chú ý là vấn đề cơ bản chỉ phát sinh bởi vì, về mặt lịch sử, rất nhiều dbms đã khá nghèo khi nói đến việc xử lý các phép nối (MySQL là một ví dụ đặc biệt đáng chú ý). Vì vậy, n + 1 có, thường, đáng chú ý là nhanh hơn tham gia. Và sau đó có nhiều cách để cải thiện trên n + 1 nhưng vẫn không cần tham gia, đó là những gì vấn đề ban đầu liên quan đến.

Tuy nhiên, MySQL bây giờ là tốt hơn rất nhiều so với nó được sử dụng để được khi nói đến tham gia. Khi lần đầu tiên tôi học MySQL, tôi đã sử dụng rất nhiều. Sau đó, tôi phát hiện ra chúng chậm như thế nào, và chuyển sang n + 1 trong mã thay vào đó. Nhưng, gần đây, tôi đã quay trở lại để tham gia, bởi vì MySQL bây giờ là một heck tốt hơn rất nhiều trong việc xử lý chúng hơn là khi tôi lần đầu tiên bắt đầu sử dụng nó.

Những ngày này, việc tham gia đơn giản vào một nhóm bảng được lập chỉ mục đúng là hiếm khi xảy ra sự cố, về mặt hiệu suất. Và nếu nó cho một hit hiệu suất, thì việc sử dụng các gợi ý chỉ mục thường giải quyết chúng.

Điều này được thảo luận ở đây bởi một trong những nhóm phát triển MySQL:

http://jorgenloland.blogspot.co.uk/2013/02/dbt-3-q3-6-x-performance-in-mysql-5610.html

Vì vậy, tóm tắt là: Nếu bạn đã tránh tham gia trong quá khứ vì hiệu suất vô cùng của MySQL với họ, sau đó thử lại trên các phiên bản mới nhất. Có thể bạn sẽ ngạc nhiên.


33
2018-01-08 12:49



Gọi các phiên bản đầu của MySQL một DBMS quan hệ là khá căng ... Nếu mọi người gặp phải những vấn đề đó đã sử dụng một cơ sở dữ liệu thực sự, họ sẽ không gặp phải những vấn đề đó. ;-) - Craig
Thật thú vị, nhiều loại vấn đề này đã được giải quyết trong MySQL với phần giới thiệu và tối ưu hóa tiếp theo của động cơ INNODB, nhưng bạn vẫn sẽ gặp phải những người đang cố gắng quảng bá MYISAM vì họ nghĩ nó nhanh hơn. - Craig
FYI, một trong 3 thông thường JOIN các thuật toán được sử dụng trong RDBMS 'được gọi là vòng lặp lồng nhau. Về cơ bản nó là một N + 1 chọn dưới mui xe. Sự khác biệt duy nhất là DB đã thực hiện một sự lựa chọn thông minh để sử dụng nó dựa trên thống kê và chỉ mục, thay vì mã máy khách buộc nó xuống đường dẫn đó một cách categorically. - Brandon
@ Brandon Có! Giống như JOIN gợi ý và gợi ý INDEX, buộc một đường dẫn thực hiện nhất định trong mọi trường hợp hiếm khi sẽ đánh bại cơ sở dữ liệu. Cơ sở dữ liệu hầu như luôn luôn rất, rất tốt khi lựa chọn phương pháp tối ưu để lấy dữ liệu. Có thể trong những ngày đầu của dbs bạn cần phải 'cụm từ' câu hỏi của bạn theo một cách đặc biệt để dụ dỗ db, nhưng sau nhiều thập kỷ kỹ thuật đẳng cấp thế giới, bây giờ bạn có thể đạt được hiệu suất tốt nhất bằng cách hỏi cơ sở dữ liệu của bạn một câu hỏi quan hệ và để nó phân loại cách lấy và lắp ráp dữ liệu đó cho bạn. - Dogs
Không chỉ là cơ sở dữ liệu sử dụng các chỉ số và thống kê, tất cả các hoạt động cũng là I / O cục bộ, phần lớn trong số đó thường hoạt động dựa trên bộ nhớ đệm hiệu quả cao hơn là đĩa. Các lập trình viên cơ sở dữ liệu dành rất nhiều sự chú ý để tối ưu hóa những thứ này. - Craig


Chúng tôi đã rời khỏi ORM ở Django vì vấn đề này. Về cơ bản, nếu bạn cố gắng và làm

for p in person:
    print p.car.colour

ORM sẽ vui vẻ trả về tất cả mọi người (thường là các cá thể của một đối tượng Person), nhưng sau đó nó sẽ cần truy vấn bảng ô tô cho mỗi Person.

Một cách tiếp cận đơn giản và rất hiệu quả cho điều này là thứ tôi gọi là "fanfolding", tránh ý tưởng vô nghĩa mà các kết quả truy vấn từ một cơ sở dữ liệu quan hệ nên ánh xạ trở lại các bảng ban đầu mà từ đó truy vấn được tạo ra.

Bước 1: Chọn rộng

  select * from people_car_colour; # this is a view or sql function

Điều này sẽ trả lại một cái gì đó như

  p.id | p.name | p.telno | car.id | car.type | car.colour
  -----+--------+---------+--------+----------+-----------
  2    | jones  | 2145    | 77     | ford     | red
  2    | jones  | 2145    | 1012   | toyota   | blue
  16   | ashby  | 124     | 99     | bmw      | yellow

Bước 2: Xác định

Hút kết quả vào một đối tượng chung chung với một đối số để chia sau mục thứ ba. Điều này có nghĩa là đối tượng "jones" sẽ không được thực hiện nhiều lần.

Bước 3: Render

for p in people:
    print p.car.colour # no more car queries

Xem trang web này để thực hiện fanfolding cho python.


25
2018-06-09 21:18



Tôi rất vui vì đã vấp phải bài đăng của bạn, bởi vì tôi nghĩ tôi đã phát điên. khi tôi phát hiện ra vấn đề N + 1, suy nghĩ ngay lập tức của tôi là tốt, tại sao bạn không tạo một khung nhìn chứa tất cả thông tin bạn cần và kéo từ chế độ xem đó? bạn đã xác nhận vị trí của tôi. cảm ơn ngài. - a developer
Chúng tôi đã rời khỏi ORM ở Django vì vấn đề này. Huh? Django có select_related, có nghĩa là để giải quyết vấn đề này - trên thực tế, tài liệu của nó bắt đầu bằng ví dụ tương tự như p.car.colour thí dụ. - Adrian17
Đây là một anwswer cũ, chúng tôi có select_related() và prefetch_related() ở Django bây giờ. - Mariusz Jamro


Giả sử bạn có COMPANY và EMPLOYEE. CÔNG TY có nhiều NHÂN VIÊN (tức là EMPLOYEE có một trường COMPANY_ID).

Trong một số cấu hình O / R, khi bạn có đối tượng Company được ánh xạ và truy cập vào đối tượng Employee, công cụ O / R sẽ thực hiện một lựa chọn cho mỗi nhân viên, nếu bạn chỉ làm những việc trong SQL thẳng, bạn có thể select * from employees where company_id = XX. Như vậy N (số nhân viên) cộng 1 (công ty)

Đây là cách các phiên bản ban đầu của EJB Entity Beans hoạt động. Tôi tin những thứ như Hibernate đã làm được với điều này, nhưng tôi không chắc lắm. Hầu hết các công cụ thường bao gồm thông tin về chiến lược lập bản đồ của chúng.


16
2017-09-18 21:33





Đây là mô tả hay về vấn đề - http://www.realsolve.co.uk/site/tech/hib-tip-pitfall.php?name=why-lazy

Bây giờ bạn đã hiểu được vấn đề, nó thường có thể tránh được bằng cách thực hiện tìm nạp tham gia trong truy vấn của bạn. Điều này về cơ bản lực lượng lấy của đối tượng tải lười biếng để dữ liệu được lấy ra trong một truy vấn thay vì truy vấn n + 1. Hi vọng điêu nay co ich.


13
2017-09-18 21:43





Kiểm tra bài đăng Ayende về chủ đề: Chống lại vấn đề Chọn N + 1 Trong NHibernate

Về cơ bản, khi sử dụng ORM như NHibernate hoặc EntityFramework, nếu bạn có mối quan hệ một (nhiều chi tiết) và muốn liệt kê tất cả chi tiết cho mỗi bản ghi chủ, bạn phải thực hiện các cuộc gọi truy vấn N + 1 đến cơ sở dữ liệu, "N" là số lượng bản ghi chính: 1 truy vấn để có được tất cả các bản ghi chính và N truy vấn, một bản ghi trên mỗi bản ghi chính, để có được tất cả chi tiết cho mỗi bản ghi chính.

Các cuộc gọi truy vấn cơ sở dữ liệu khác -> thời gian chờ nhiều hơn -> giảm hiệu suất của ứng dụng / cơ sở dữ liệu.

Tuy nhiên, ORM có các tùy chọn để tránh vấn đề này, chủ yếu là sử dụng "tham gia".


12
2018-06-05 22:21



tham gia không phải là một giải pháp tốt (thường), bởi vì chúng có thể dẫn đến một sản phẩm Descartes, có nghĩa là số lượng các hàng kết quả là số kết quả của bảng gốc nhân với số lượng kết quả trong mỗi bảng con. đặc biệt xấu trên nhiều cấp độ tình trạng hỗn loạn. Chọn 20 "blog" với 100 "bài đăng" trên mỗi bài đăng và 10 "nhận xét" trên mỗi bài đăng sẽ dẫn đến 20000 hàng kết quả. NHibernate có cách giải quyết, giống như "batch-size" (chọn trẻ em có mệnh đề trong id mẹ) hoặc "subselect". - Erik Hart


Theo tôi, bài viết được viết bằng Hibernate Pitfall: Tại sao mối quan hệ nên lười biếng hoàn toàn trái ngược với vấn đề N + 1 thực.

Nếu bạn cần giải thích đúng, vui lòng tham khảo Hibernate - Chương 19: Cải thiện hiệu suất - Tìm kiếm các chiến lược

Chọn tìm nạp (mặc định) là   cực kỳ dễ bị N + 1 chọn   vấn đề, vì vậy chúng tôi có thể muốn bật   tham gia tìm nạp


11
2017-07-21 11:55



tôi đọc trang ngủ đông. Nó không nói những gì N + 1 chọn vấn đề thực ra Là. Nhưng nó nói rằng bạn có thể sử dụng tham gia để sửa chữa nó. - Ian Boyd
kích thước lô là bắt buộc đối với việc tìm nạp lựa chọn, để chọn các đối tượng con cho nhiều cha mẹ trong một câu lệnh chọn. Lựa chọn có thể là một lựa chọn khác. Tham gia có thể trở nên thực sự tồi tệ nếu bạn có nhiều cấp bậc phân cấp và một sản phẩm được tạo ra. - Erik Hart


Liên kết được cung cấp có một ví dụ rất đơn giản về vấn đề n + 1. Nếu bạn áp dụng nó cho Hibernate nó về cơ bản nói về cùng một điều. Khi bạn truy vấn đối tượng, thực thể được tải nhưng mọi liên kết (trừ khi được định cấu hình khác) sẽ bị tải xuống. Do đó, một truy vấn cho các đối tượng gốc và một truy vấn khác để tải các kết hợp cho mỗi đối tượng này. 100 đối tượng được trả lại có nghĩa là một truy vấn ban đầu và sau đó là 100 truy vấn bổ sung để có được liên kết cho mỗi, n + 1.

http://pramatr.com/2009/02/05/sql-n-1-selects-explained/


9
2018-02-20 08:33