Pertanyaan Apakah masalah kueri SELECT N + 1?


SELECT N + 1 umumnya dinyatakan sebagai masalah dalam pemetaan Object-Relational mapping (ORM), dan saya mengerti bahwa ada sesuatu yang harus dilakukan dengan harus membuat banyak query database untuk sesuatu yang tampak sederhana di dunia objek.

Adakah yang punya penjelasan lebih rinci tentang masalah ini?


1297
2017-09-18 21:30


asal


Jawaban:


Katakanlah Anda memiliki koleksi Car objek (baris database), dan masing-masing Car memiliki koleksi Wheel objek (juga baris). Dengan kata lain, Car -> Wheel adalah hubungan 1-ke-banyak.

Sekarang, katakanlah Anda perlu melakukan iterasi melalui semua mobil, dan untuk setiap mobil, cetak daftar roda. Penerapan O / R yang naif akan melakukan hal berikut:

SELECT * FROM Cars;

Lalu untuk setiap Car:

SELECT * FROM Wheel WHERE CarId = ?

Dengan kata lain, Anda memiliki satu pilihan untuk Mobil, dan kemudian N tambahan memilih, di mana N adalah jumlah total mobil.

Atau, seseorang bisa mendapatkan semua roda dan melakukan pencarian di memori:

SELECT * FROM Wheel

Ini mengurangi jumlah perjalanan pulang pergi ke database dari N +1 menjadi 2. Kebanyakan alat ORM memberi Anda beberapa cara untuk mencegah pemilihan N + 1.

Referensi: Java Persistence dengan Hibernate, bab 13.


767
2017-09-18 21:36



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

Itu memberi Anda hasil set di mana baris anak di tabel2 menyebabkan duplikasi dengan mengembalikan hasil tabel1 untuk setiap baris anak di tabel2. Pemetaan O / R harus membedakan contoh tabel1 berdasarkan bidang kunci unik, kemudian gunakan semua kolom table2 untuk mengisi instance turunan.

SELECT table1.*

SELECT table2.* WHERE SomeFkId = #

N + 1 adalah tempat kueri pertama mengisi objek utama dan kueri kedua mengisi semua objek turunan untuk masing-masing objek utama unik yang dikembalikan.

Mempertimbangkan:

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

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

dan tabel dengan struktur serupa. Kueri tunggal untuk alamat "22 Valley St" dapat kembali:

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

O / RM harus mengisi instance Home dengan ID = 1, Address = "22 Valley St" dan kemudian mengisi array Inhabitants dengan instance People untuk Dave, John, dan Mike hanya dengan satu query.

Permintaan N + 1 untuk alamat yang sama yang digunakan di atas akan menghasilkan:

Id Address
1  22 Valley St

dengan permintaan terpisah seperti

SELECT * FROM Person WHERE HouseId = 1

dan menghasilkan satu set data terpisah seperti

Name    HouseId
Dave    1
John    1
Mike    1

dan hasil akhirnya sama dengan di atas dengan kueri tunggal.

Keuntungan untuk memilih satu adalah bahwa Anda mendapatkan semua data di depan yang mungkin menjadi apa yang Anda inginkan. Keuntungan untuk N + 1 adalah kompleksitas query berkurang dan Anda dapat menggunakan pemuatan malas di mana set hasil anak hanya dimuat saat permintaan pertama.


98
2017-09-18 21:43



Pemasok dengan hubungan satu-ke-banyak dengan Produk. Satu Pemasok memiliki (persediaan) banyak Produk.

***** 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      |
+-----+-----------+--------------------+-------+------------+

Faktor:

  • Mode malas untuk Pemasok diatur ke "benar" (default)

  • Mode pengambilan yang digunakan untuk kueri pada Produk adalah Pilih

  • Mode ambil (default): Informasi pemasok diakses

  • Caching tidak memainkan peran untuk pertama kalinya

  • Pemasok diakses

Modus ambil adalah Pilih Fetch (standar)

// 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=?

Hasil:

  • 1 pilih pernyataan untuk Produk
  • N pilih pernyataan untuk Pemasok

Ini adalah masalah pilih N +1!


58
2017-12-01 13:35



Saya tidak dapat berkomentar langsung tentang jawaban lain, karena saya tidak memiliki reputasi yang cukup. Tapi perlu dicatat bahwa masalah itu pada dasarnya hanya muncul karena, secara historis, banyak dbms sangat buruk ketika berurusan dengan penanganan gabungan (MySQL menjadi contoh yang sangat penting). Jadi n + 1 biasanya lebih cepat daripada gabungan. Dan kemudian ada cara untuk meningkatkan pada n + 1 tetapi masih tanpa perlu bergabung, yang merupakan masalah asli yang berkaitan dengannya.

Namun, MySQL sekarang jauh lebih baik daripada dulu ketika bergabung. Ketika saya pertama kali belajar MySQL, saya sering bergabung. Kemudian saya menemukan betapa lambatnya mereka, dan beralih ke n +1 di kode sebagai gantinya. Tapi, baru-baru ini, saya sudah pindah kembali untuk bergabung, karena MySQL sekarang menjadi lebih baik dalam menangani mereka daripada saat pertama kali saya menggunakannya.

Hari-hari ini, bergabung sederhana pada satu set tabel yang diindeks dengan tepat jarang masalah, dalam hal kinerja. Dan jika itu memberikan pukulan kinerja, maka penggunaan petunjuk indeks sering memecahkannya.

Ini dibahas di sini oleh salah satu tim pengembangan MySQL:

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

Jadi ringkasannya adalah: Jika Anda menghindari bergabung di masa lalu karena kinerja buruk MySQL dengan mereka, kemudian coba lagi pada versi terbaru. Anda mungkin akan terkejut.


33
2018-01-08 12:49



Kami pindah dari ORM di Django karena masalah ini. Pada dasarnya, jika Anda mencoba dan melakukan

for p in person:
    print p.car.colour

ORM akan dengan senang hati mengembalikan semua orang (biasanya sebagai contoh dari objek Person), tetapi kemudian akan perlu query meja mobil untuk setiap Orang.

Pendekatan yang sederhana dan sangat efektif untuk ini adalah sesuatu yang saya sebut "fanfolding", yang menghindari ide tidak masuk akal bahwa hasil kueri dari basis data relasional harus memetakan kembali ke tabel asli dari mana kueri disusun.

Langkah 1: Lebar pilih

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

Ini akan mengembalikan sesuatu seperti

  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

Langkah 2: Objectify

Menghisap hasil ke dalam pencipta objek generik dengan argumen untuk memisahkan setelah item ketiga. Ini berarti bahwa objek "jones" tidak akan dibuat lebih dari satu kali.

Langkah 3: Render

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

Lihat halaman web ini untuk implementasi fanfolding untuk python.


25
2018-06-09 21:18



Misalkan Anda memiliki PERUSAHAAN dan EMPLOYEE. PERUSAHAAN memiliki banyak EMPLOYEES (yaitu EMPLOYEE memiliki bidang COMPANY_ID).

Dalam beberapa konfigurasi O / R, ketika Anda memiliki objek Perusahaan yang dipetakan dan pergi untuk mengakses objek Karyawannya, alat O / R akan melakukan satu pilihan untuk setiap karyawan, yang mana jika Anda hanya melakukan hal-hal dalam SQL lurus, Anda dapat select * from employees where company_id = XX. Jadi N (# karyawan) ditambah 1 (perusahaan)

Beginilah cara versi awal EJB Entity Beans bekerja. Saya percaya hal-hal seperti Hibernate telah menyelesaikan ini, tapi saya tidak terlalu yakin. Kebanyakan alat biasanya menyertakan informasi mengenai strategi pemetaan mereka.


16
2017-09-18 21:33



Berikut penjelasan yang bagus tentang masalah - http://www.realsolve.co.uk/site/tech/hib-tip-pitfall.php?name=why-lazy

Sekarang Anda memahami masalah itu biasanya dapat dihindari dengan melakukan penggabungan gabung dalam kueri Anda. Ini pada dasarnya memaksa pengambilan dari objek yang dimuat dengan malas sehingga data diambil dalam satu query, bukan n + 1 queries. Semoga ini membantu.


14
2017-09-18 21:43