Baris 'Aktual' yang tidak akurat diperhitungkan dalam paket paralel


17

Ini adalah pertanyaan yang murni akademis, sehingga tidak menimbulkan masalah dan saya hanya tertarik untuk mendengar penjelasan tentang perilaku tersebut.

Ambil masalah standar Itzik Ben-Gan silang bergabung dengan tabel penghitungan CTE:

USE [master]
GO

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

CREATE FUNCTION [dbo].[TallyTable] 
(   
    @N INT
)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN 
(
    WITH 
    E1(N) AS 
    (
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
    )                                       -- 1*10^1 or 10 rows
    , E2(N) AS (SELECT 1 FROM E1 a, E1 b)   -- 1*10^2 or 100 rows
    , E4(N) AS (SELECT 1 FROM E2 a, E2 b)   -- 1*10^4 or 10,000 rows
    , E8(N) AS (SELECT 1 FROM E4 a, E4 b)   -- 1*10^8 or 100,000,000 rows

    SELECT TOP (@N) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS N FROM E8 
)
GO

Mengeluarkan kueri yang akan membuat tabel angka 1 juta baris:

SELECT
    COUNT(N)
FROM
    dbo.TallyTable(1000000) tt

Lihatlah rencana eksekusi paralel untuk kueri ini:

Rencana eksekusi paralel

Perhatikan jumlah baris 'aktual' sebelum operator aliran pengumpulan adalah 1.004.588. Setelah operator aliran pengumpulan, jumlah baris adalah 1.000.000 yang diharapkan. Lebih aneh lagi, nilainya tidak konsisten dan akan bervariasi dari satu menjalankan ke menjalankan. Hasil COUNT selalu benar.

Keluarkan kueri lagi, yang memaksa paket non-paralel:

SELECT
    COUNT(N)
FROM
    dbo.TallyTable(1000000) tt
OPTION (MAXDOP 1)

Kali ini semua operator menunjukkan jumlah baris 'aktual' yang benar.

Rencana eksekusi non-paralel

Saya sudah mencoba ini pada 2005SP3 dan 2008R2 sejauh ini, hasil yang sama pada keduanya. Adakah pemikiran tentang apa yang menyebabkan ini?

Jawaban:


12

Baris dilewatkan melintasi pertukaran secara internal dari produsen ke utas konsumen dalam bentuk paket (karenanya CXPACKET - paket pertukaran kelas), bukan baris-per-satu-waktu. Ada sejumlah buffering di dalam bursa. Juga, panggilan untuk mematikan pipa dari sisi konsumen Gather Streams harus diteruskan dalam paket kontrol kembali ke utas produsen. Penjadwalan dan pertimbangan internal lainnya berarti bahwa rencana paralel selalu memiliki 'jarak berhenti' tertentu.

Sebagai akibatnya, Anda akan sering melihat perbedaan jumlah baris seperti ini di mana sebenarnya kurang dari seluruh potensi rowset dari sub-tree. Dalam hal ini, TOP membawa eksekusi ke 'awal akhir'.

Informasi lebih lanjut:


10

Saya pikir saya mungkin memiliki penjelasan parsial untuk ini, tetapi jangan ragu untuk menembaknya atau memposting alternatif lain. @MartinSmith jelas melakukan sesuatu dengan menyoroti efek TOP dalam rencana eksekusi.

Sederhananya, 'Hitungan Baris Aktual' bukan hitungan dari baris yang diproses oleh operator, ini adalah berapa kali metode GetNext () dari operator dipanggil.

Diambil dari BOL :

Operator fisik menginisialisasi, mengumpulkan data, dan menutup. Secara khusus, operator fisik dapat menjawab tiga panggilan metode berikut:

  • Init (): Metode Init () menyebabkan operator fisik menginisialisasi sendiri dan mengatur struktur data yang diperlukan. Operator fisik dapat menerima banyak panggilan Init (), meskipun biasanya operator fisik hanya menerima satu.
  • GetNext (): Metode GetNext () menyebabkan operator fisik mendapatkan baris data pertama atau berikutnya. Operator fisik dapat menerima nol atau banyak panggilan GetNext ().
  • Close (): Metode Close () menyebabkan operator fisik melakukan beberapa operasi pembersihan dan mematikannya sendiri. Operator fisik hanya menerima satu panggilan Tutup ().

Metode GetNext () mengembalikan satu baris data, dan berapa kali itu disebut muncul sebagai ActualRows dalam output Showplan yang dihasilkan dengan menggunakan SET STATISTIK PROFILI HIDUP atau SET STATISTIK XML ON.

Demi kelengkapan, sedikit latar belakang pada operator paralel berguna. Pekerjaan didistribusikan ke beberapa aliran dalam rencana paralel oleh aliran partisi ulang atau mendistribusikan operator aliran. Ini mendistribusikan baris atau halaman antara utas menggunakan salah satu dari empat mekanisme:

  • Hash mendistribusikan baris berdasarkan hash dari kolom di baris
  • Round-robin mendistribusikan baris dengan mengulangi daftar utas dalam satu lingkaran
  • Siaran mendistribusikan semua halaman atau baris ke semua utas
  • Partisi permintaan hanya digunakan untuk pemindaian. Utas berputar, meminta halaman data dari operator, memprosesnya dan meminta halaman lebih lanjut ketika selesai.

Operator aliran terdistribusi pertama (paling kanan dalam rencana) menggunakan partisi permintaan pada baris yang berasal dari pemindaian konstan. Ada tiga utas yang memanggil GetNext () 6, 4 dan 0 kali dengan total 10 'Baris Aktual':

<RunTimeInformation>
       <RunTimeCountersPerThread Thread="2" ActualRows="6" ActualEndOfScans="1" ActualExecutions="1" />
       <RunTimeCountersPerThread Thread="1" ActualRows="4" ActualEndOfScans="1" ActualExecutions="1" />
       <RunTimeCountersPerThread Thread="0" ActualRows="0" ActualEndOfScans="0" ActualExecutions="0" />
 </RunTimeInformation>

Di operator distribusi berikutnya kami memiliki tiga utas lagi, kali ini dengan 50, 50 dan 0 panggilan ke GetNext () dengan total 100:

<RunTimeInformation>
    <RunTimeCountersPerThread Thread="2" ActualRows="50" ActualEndOfScans="1" ActualExecutions="1" />
    <RunTimeCountersPerThread Thread="1" ActualRows="50" ActualEndOfScans="1" ActualExecutions="1" />
    <RunTimeCountersPerThread Thread="0" ActualRows="0" ActualEndOfScans="0" ActualExecutions="0" />
</RunTimeInformation>

Ini di operator paralel berikutnya yang kemungkinan penyebab dan penjelasannya muncul.

<RunTimeInformation>
    <RunTimeCountersPerThread Thread="2" ActualRows="1" ActualEndOfScans="0" ActualExecutions="1" />
    <RunTimeCountersPerThread Thread="1" ActualRows="10" ActualEndOfScans="0" ActualExecutions="1" />
    <RunTimeCountersPerThread Thread="0" ActualRows="0" ActualEndOfScans="0" ActualExecutions="0" />
</RunTimeInformation>

Jadi kami sekarang memiliki 11 panggilan ke GetNext (), di mana kami mengharapkan untuk melihat 10.

Edit: 2011-11-13

Terjebak pada titik ini, saya pergi mencari jawaban dengan bab-bab dalam indeks berkerumun dan @MikeWalsh dengan ramah mengarahkan @SQLKiwi di sini .


7

1,004,588 adalah angka yang banyak muncul dalam pengujian saya juga.

Saya juga melihat ini untuk rencana yang agak sederhana di bawah ini.

WITH 
E1(N) AS 
(
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
)                                       -- 1*10^1 or 10 rows
, E2(N) AS (SELECT 1 FROM E1 a, E1 b)   -- 1*10^2 or 100 rows
, E4(N) AS (SELECT 1 FROM E2 a, E2 b)   -- 1*10^4 or 10,000 rows
SELECT * INTO #E4 FROM E4;

WITH E8(N) AS (SELECT 1 FROM #E4 a, #E4 b),
Nums(N) AS (SELECT  TOP (1000000) ROW_NUMBER() OVER (ORDER BY (SELECT 0)) FROM E8 )
SELECT COUNT(N) FROM Nums

DROP TABLE #E4

Rencana

Figur lain yang menarik dalam rencana eksekusi adalah

+----------------------------------+--------------+--------------+-----------------+
|                                  | Table Scan A | Table Scan B | Row Count Spool |
+----------------------------------+--------------+--------------+-----------------+
| Number Of Executions             | 2            |            2 |             101 |
| Actual Number Of Rows - Total    | 101          |        20000 |         1004588 |
| Actual Number Of Rows - Thread 0 | -            |              |                 |
| Actual Number Of Rows - Thread 1 | 95           |        10000 |          945253 |
| Actual Number Of Rows - Thread 2 | 6            |        10000 |           59335 |
| Actual Rebinds                   | 0            |            0 |               2 |
| Actual Rewinds                   | 0            |            0 |              99 |
+----------------------------------+--------------+--------------+-----------------+

Dugaan saya hanya karena tugas-tugas sedang diproses secara paralel satu tugas adalah di tengah-tengah baris pemrosesan penerbangan ketika yang lain mengirimkan baris ke-sejuta ke operator aliran pengumpulan sehingga baris-baris tambahan sedang ditangani. Selain itu dari artikel ini , baris disangga dan dikirim dalam batch ke iterator ini sehingga sangat mungkin bahwa jumlah baris yang sedang diproses akan melebihi daripada tepat mengenai TOPspesifikasi dalam peristiwa apa pun.

Edit

Hanya melihat ini sedikit lebih detail. Saya perhatikan saya mendapatkan lebih banyak variasi daripada hanya jumlah 1,004,588baris yang dikutip di atas sehingga menjalankan kueri di atas dalam satu lingkaran untuk 1.000 iterasi dan menangkap rencana eksekusi yang sebenarnya. Membuang 81 hasil yang tingkat pararelismenya nol memberikan angka-angka berikut.

count       Table Scan A: Total Actual Row Spool - Total Actual Rows
----------- ------------------------------ ------------------------------
352         101                            1004588
323         102                            1004588
72          101                            1003565
37          101                            1002542
35          102                            1003565
29          101                            1001519
18          101                            1000496
13          102                            1002542
5           9964                           99634323
5           102                            1001519
4           9963                           99628185
3           10000                          100000000
3           9965                           99642507
2           9964                           99633300
2           9966                           99658875
2           9965                           99641484
1           9984                           99837989
1           102                            1000496
1           9964                           99637392
1           9968                           99671151
1           9966                           99656829
1           9972                           99714117
1           9963                           99629208
1           9985                           99847196
1           9967                           99665013
1           9965                           99644553
1           9963                           99623626
1           9965                           99647622
1           9966                           99654783
1           9963                           99625116

Dapat dilihat bahwa 1.004.588 sejauh ini merupakan hasil yang paling umum tetapi pada 3 kesempatan kasus terburuk terjadi dan 100.000.000 baris diproses. Kasus terbaik yang diamati adalah 1.000.496 hitungan baris, yang terjadi 19 kali.

Skrip lengkap untuk direproduksi ada di bagian bawah revisi 2 dari jawaban ini (perlu penyesuaian jika dijalankan pada sistem dengan lebih dari 2 prosesor).


1

Saya percaya bahwa masalahnya berasal dari fakta bahwa beberapa aliran dapat memproses baris yang sama tergantung pada bagaimana baris diukir di antara aliran.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.