Saya menjalankan perbandingan kinerja antara menggunakan 1000 pernyataan INSERT:
INSERT INTO T_TESTS (TestId, FirstName, LastName, Age)
VALUES ('6f3f7257-a3d8-4a78-b2e1-c9b767cfe1c1', 'First 0', 'Last 0', 0)
INSERT INTO T_TESTS (TestId, FirstName, LastName, Age)
VALUES ('32023304-2e55-4768-8e52-1ba589b82c8b', 'First 1', 'Last 1', 1)
...
INSERT INTO T_TESTS (TestId, FirstName, LastName, Age)
VALUES ('f34d95a7-90b1-4558-be10-6ceacd53e4c4', 'First 999', 'Last 999', 999)
..versus menggunakan pernyataan INSERT tunggal dengan 1000 nilai:
INSERT INTO T_TESTS (TestId, FirstName, LastName, Age)
VALUES
('db72b358-e9b5-4101-8d11-7d7ea3a0ae7d', 'First 0', 'Last 0', 0),
('6a4874ab-b6a3-4aa4-8ed4-a167ab21dd3d', 'First 1', 'Last 1', 1),
...
('9d7f2a58-7e57-4ed4-ba54-5e9e335fb56c', 'First 999', 'Last 999', 999)
Yang sangat mengejutkan saya, hasilnya berlawanan dengan yang saya pikirkan:
- 1000 pernyataan INSERT: 290 msec.
- 1 pernyataan INSERT dengan 1000 VALUES: 2800 msec.
Pengujian dijalankan langsung di MSSQL Management Studio dengan SQL Server Profiler yang digunakan untuk pengukuran (dan saya mendapatkan hasil serupa yang menjalankannya dari kode C # menggunakan SqlClient, yang bahkan lebih mengejutkan lagi mengingat semua lapisan DAL pulang pergi)
Apakah ini masuk akal atau dijelaskan? Kenapa, metode yang seharusnya lebih cepat menghasilkan 10 kali (!) Kinerja yang lebih buruk ?
Terima kasih.
EDIT: Melampirkan rencana eksekusi untuk keduanya:
Jawaban:
Rencana Anda menunjukkan bahwa sisipan tunggal menggunakan prosedur berparameter (mungkin diparameterisasi otomatis) sehingga waktu parse / kompilasi untuk ini harus diminimalkan.
Saya pikir saya akan melihat ini sedikit lebih banyak, jadi siapkan loop ( skrip ) dan coba sesuaikan jumlah
VALUES
klausa dan catat waktu kompilasi.Saya kemudian membagi waktu kompilasi dengan jumlah baris untuk mendapatkan waktu kompilasi rata-rata per klausa. Hasilnya di bawah ini
Hingga 250
VALUES
klausa yang ada, waktu kompilasi / jumlah klausa memiliki tren sedikit naik tetapi tidak terlalu dramatis.Tapi kemudian terjadi perubahan mendadak.
Bagian data itu ditunjukkan di bawah ini.
Ukuran rencana cache yang telah tumbuh secara linier tiba-tiba turun tetapi CompileTime meningkat 7 kali lipat dan CompileMemory meningkat. Ini adalah titik potong antara paket yang paketnya diparameterisasi otomatis (dengan 1.000 parameter) ke yang tidak diparameterisasi. Setelah itu tampaknya menjadi kurang efisien secara linier (dalam hal jumlah klausul nilai yang diproses dalam waktu tertentu).
Tidak yakin mengapa ini harus terjadi. Agaknya ketika menyusun rencana untuk nilai literal tertentu, ia harus melakukan beberapa aktivitas yang tidak berskala linier (seperti pengurutan).
Tampaknya tidak memengaruhi ukuran rencana kueri yang di-cache ketika saya mencoba kueri yang seluruhnya terdiri dari baris duplikat dan tidak memengaruhi urutan output tabel konstanta (dan saat Anda memasukkan ke dalam tumpukan waktu yang dihabiskan untuk menyortir akan menjadi sia-sia bahkan jika itu terjadi).
Selain itu, jika indeks berkerumun ditambahkan ke tabel, rencana tersebut masih menunjukkan langkah pengurutan eksplisit sehingga tampaknya tidak menyortir pada waktu kompilasi untuk menghindari pengurutan pada waktu proses.
Saya mencoba melihat ini di debugger tetapi simbol publik untuk versi SQL Server 2008 saya tampaknya tidak tersedia, jadi saya harus melihat
UNION ALL
konstruksi yang setara di SQL Server 2005.Jejak tumpukan tipikal ada di bawah
Jadi menghapus nama di pelacakan tumpukan tampaknya menghabiskan banyak waktu untuk membandingkan string.
Artikel KB ini menunjukkan yang
DeriveNormalizedGroupProperties
terkait dengan apa yang dulu disebut tahap normalisasi pemrosesan kueriTahap ini sekarang disebut mengikat atau algebrizing dan mengambil keluaran pohon parse ekspresi dari tahap penguraian sebelumnya dan keluaran pohon ekspresi algebrized (pohon prosesor kueri) untuk maju ke pengoptimalan (pengoptimalan rencana trivial dalam kasus ini) [ref] .
Saya mencoba satu eksperimen lagi ( Script ) yang akan menjalankan ulang pengujian asli tetapi melihat tiga kasus berbeda.
Dapat dilihat dengan jelas bahwa semakin panjang string semakin buruk hal-hal dan sebaliknya semakin banyak duplikat semakin baik hal-hal. Seperti yang disebutkan sebelumnya, duplikat tidak memengaruhi ukuran rencana yang di-cache, jadi saya berasumsi bahwa harus ada proses identifikasi duplikat saat membangun pohon ekspresi algebrized itu sendiri.
Edit
Satu tempat di mana informasi ini dimanfaatkan ditunjukkan oleh @Lieven di sini
Karena pada waktu kompilasi dapat menentukan bahwa
Name
kolom tidak memiliki duplikat, pengurutannya dilewati oleh1/ (ID - ID)
ekspresi sekunder pada waktu proses (pengurutan dalam rencana hanya memiliki satuORDER BY
kolom) dan tidak ada kesalahan bagi dengan nol yang dimunculkan. Jika duplikat ditambahkan ke tabel maka operator pengurutan memperlihatkan dua urutan menurut kolom dan kesalahan yang diharapkan dimunculkan.sumber
<ParameterList>
daripada yang memiliki<ConstantScan><Values><Row>
daftar.<ConstantScan><Values><Row>
sebagai pengganti<ParameterList>
.Hal ini tidak terlalu mengejutkan: rencana eksekusi untuk sisipan kecil dihitung sekali, dan kemudian digunakan kembali 1000 kali. Mengurai dan menyiapkan rencana itu cepat, karena hanya memiliki empat nilai untuk dipahami. Rencana 1000 baris, di sisi lain, perlu menangani 4000 nilai (atau 4000 parameter jika Anda membuat parameter pada tes C # Anda). Ini dapat dengan mudah menghabiskan penghematan waktu yang Anda peroleh dengan menghilangkan 999 perjalanan bolak-balik ke SQL Server, terutama jika jaringan Anda tidak terlalu lambat.
sumber
Masalahnya mungkin berkaitan dengan waktu yang dibutuhkan untuk mengompilasi kueri.
Jika Anda ingin mempercepat penyisipan, yang benar-benar perlu Anda lakukan adalah membungkusnya dalam sebuah transaksi:
Dari C #, Anda juga dapat mempertimbangkan untuk menggunakan parameter nilai tabel. Menerbitkan beberapa perintah dalam satu batch, dengan memisahkannya dengan titik koma, adalah pendekatan lain yang juga akan membantu.
sumber
Saya mengalami situasi serupa saat mencoba mengubah tabel dengan beberapa baris 100k dengan program C ++ (MFC / ODBC).
Karena operasi ini memakan waktu yang sangat lama, saya membayangkan menggabungkan beberapa sisipan menjadi satu (hingga 1000 karena keterbatasan MSSQL ). Dugaan saya bahwa banyak pernyataan penyisipan tunggal akan menciptakan overhead yang mirip dengan apa yang dijelaskan di sini .
Namun, ternyata konversi tersebut memakan waktu lebih lama:
Jadi, 1000 panggilan tunggal ke CDatabase :: ExecuteSql masing-masing dengan satu pernyataan INSERT (metode 1) kira-kira dua kali lebih cepat dari satu panggilan ke CDatabase :: ExecuteSql dengan pernyataan INSERT multi-baris dengan 1000 tupel nilai (metode 2).
Pembaruan: Jadi, hal berikutnya yang saya coba adalah menggabungkan 1000 pernyataan INSERT terpisah menjadi satu string dan meminta server menjalankannya (metode 3). Ternyata ini lebih cepat daripada metode 1.
Sunting: Saya menggunakan Microsoft SQL Server Express Edition (64-bit) v10.0.2531.0
sumber