Database
 sql >> Teknologi Basis Data >  >> RDS >> Database

Parameterisasi Sederhana dan Rencana Trivial — Bagian 2

Jenis Data Parameter

Seperti yang disebutkan di bagian pertama seri ini, salah satu alasan lebih baik untuk membuat parameter secara eksplisit adalah agar Anda memiliki kontrol penuh atas tipe data parameter. Parameterisasi sederhana memiliki sejumlah keanehan di area ini, yang dapat mengakibatkan lebih banyak rencana berparameter di-cache daripada yang diharapkan, atau menemukan hasil yang berbeda dibandingkan dengan versi tanpa parameter.

Ketika SQL Server menerapkan parameterisasi sederhana untuk pernyataan ad-hoc, itu membuat tebakan tentang tipe data dari parameter pengganti. Saya akan membahas alasan menebaknya nanti di seri ini.

Untuk saat ini, mari kita lihat beberapa contoh menggunakan database Stack Overflow 2010 pada SQL Server 2019 CU 14. Kompatibilitas database diatur ke 150, dan ambang biaya untuk paralelisme diatur ke 50 untuk menghindari paralelisme untuk saat ini:

ALTER DATABASE SCOPED CONFIGURATION 
    CLEAR PROCEDURE_CACHE;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 252;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 25221;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 252552;

Pernyataan ini menghasilkan enam paket yang di-cache, tiga Adhoc dan tiga Disiapkan :

Jenis tebakan berbeda

Perhatikan tipe data parameter yang berbeda di Prepared rencana.

Inferensi Tipe Data

Rincian bagaimana setiap tipe data ditebak rumit dan didokumentasikan secara tidak lengkap. Sebagai titik awal, SQL Server menyimpulkan tipe dasar dari representasi tekstual nilai, kemudian menggunakan subtipe terkecil yang kompatibel.

Untuk string angka tanpa tanda kutip atau titik desimal, SQL Server memilih dari tinyint , smallint , dan integer . Untuk angka seperti itu di luar rentang integer , SQL Server menggunakan numeric dengan ketelitian sekecil mungkin. Misalnya, angka 2.147.483.648 diketik sebagai numeric(10,0) . bigint type tidak digunakan untuk parameterisasi sisi server. Paragraf ini menjelaskan tipe data yang dipilih dalam contoh sebelumnya.

String angka dengan titik desimal ditafsirkan sebagai numeric , dengan presisi dan skala yang cukup besar untuk menampung nilai yang diberikan. String yang diawali dengan simbol mata uang ditafsirkan sebagai money . String dalam notasi ilmiah diterjemahkan menjadi float . smallmoney dan real jenis tidak digunakan.

datetime dan uniqueidentifer jenis tidak dapat disimpulkan dari format string alami. Untuk mendapatkan datetime atau uniqueidentifier jenis parameter, nilai literal harus disediakan dalam format escape ODBC. Misalnya {d '1901-01-01'} , {ts '1900-01-01 12:34:56.790'} , atau {guid 'F85C72AB-15F7-49E9-A949-273C55A6C393'} . Jika tidak, tanggal yang dimaksud atau literal UUID diketik sebagai string. Jenis tanggal dan waktu selain datetime tidak digunakan.

String umum dan literal biner diketik sebagai varchar(8000) , nvarchar(4000) , atau varbinary(8000) sebagaimana mestinya, kecuali jika literal melebihi 8000 byte dalam hal ini max varian yang digunakan. Skema ini membantu menghindari polusi cache dan penggunaan ulang tingkat rendah yang akan dihasilkan dari penggunaan panjang tertentu.

Tidak mungkin menggunakan CAST atau CONVERT untuk mengatur tipe data untuk parameter karena alasan yang akan saya jelaskan nanti di seri ini. Ada contohnya di bagian selanjutnya.

Saya tidak akan membahas parameterisasi paksa dalam seri ini, tetapi saya ingin menyebutkan aturan untuk inferensi tipe data dalam hal ini memiliki beberapa perbedaan penting dibandingkan dengan parameterisasi sederhana . Parameterisasi paksa tidak ditambahkan hingga SQL Server 2005, jadi Microsoft memiliki kesempatan untuk memasukkan beberapa pelajaran dari parameterisasi sederhana pengalaman, dan tidak perlu terlalu khawatir tentang masalah kompatibilitas mundur.

Tipe Numerik

Untuk bilangan dengan titik desimal dan bilangan bulat di luar rentang integer , aturan tipe yang disimpulkan menghadirkan masalah khusus untuk penggunaan kembali rencana dan polusi cache.

Pertimbangkan kueri berikut menggunakan desimal:

ALTER DATABASE SCOPED CONFIGURATION 
    CLEAR PROCEDURE_CACHE;
GO
DROP TABLE IF EXISTS dbo.Test;
GO
CREATE TABLE dbo.Test
(
    SomeValue decimal(19,8) NOT NULL
);
GO
SELECT 
    T.SomeValue 
FROM dbo.Test AS T 
WHERE 
    T.SomeValue >= 987.65432 
    AND T.SomeValue < 123456.789;

Kueri ini memenuhi syarat untuk parameterisasi sederhana . SQL Server memilih presisi dan skala terkecil untuk parameter yang dapat memuat nilai yang diberikan. Ini berarti ia memilih numeric(8,5) untuk 987.65432 dan numeric(9,3) untuk 123456.789 :

Tipe data numerik yang disimpulkan

Jenis yang disimpulkan ini tidak cocok dengan decimal(19,8) jenis kolom, sehingga konversi di sekitar parameter muncul dalam rencana eksekusi:

Konversi ke jenis kolom

Konversi ini hanya menunjukkan inefisiensi waktu proses kecil dalam kasus khusus ini. Dalam situasi lain, ketidakcocokan antara tipe data kolom dan tipe parameter yang disimpulkan dapat mencegah pencarian indeks atau memerlukan SQL Server untuk melakukan pekerjaan ekstra untuk membuat pencarian dinamis.

Bahkan jika rencana eksekusi yang dihasilkan tampak masuk akal, ketidakcocokan tipe dapat dengan mudah mempengaruhi kualitas rencana karena efek ketidakcocokan tipe pada estimasi kardinalitas. Itu selalu yang terbaik untuk menggunakan tipe data yang cocok, dan untuk memperhatikan tipe turunan yang dihasilkan dari ekspresi.

Rencanakan Penggunaan Kembali

Masalah utama dengan paket saat ini adalah jenis kesimpulan spesifik yang memengaruhi pencocokan paket yang di-cache dan oleh karena itu digunakan kembali. Mari kita jalankan beberapa kueri lagi dengan bentuk umum yang sama:

SELECT 
    T.SomeValue 
FROM dbo.Test AS T 
WHERE 
    T.SomeValue >= 98.76 
    AND T.SomeValue < 123.4567;
GO
SELECT 
    T.SomeValue 
FROM dbo.Test AS T 
WHERE 
    T.SomeValue >= 1.2 
    AND T.SomeValue < 1234.56789;
GO

Sekarang lihat cache paket:

SELECT
    CP.usecounts,
    CP.objtype,
    ST.[text]
FROM sys.dm_exec_cached_plans AS CP
CROSS APPLY sys.dm_exec_sql_text (CP.plan_handle) AS ST
WHERE 
    ST.[text] NOT LIKE '%dm_exec_cached_plans%'
    AND ST.[text] LIKE '%SomeValue%Test%'
ORDER BY 
    CP.objtype ASC;

Ini menunjukkan AdHoc dan Disiapkan pernyataan untuk setiap kueri yang kami kirimkan:

Pernyataan terpisah yang disiapkan

Teks parameternya sama, tetapi tipe data parameternya berbeda, jadi paket terpisah di-cache, dan tidak ada penggunaan ulang paket.

Jika kami terus mengirimkan kueri dengan kombinasi skala atau presisi yang berbeda, Disiapkan . baru rencana akan dibuat dan di-cache setiap kali. Ingat tipe kesimpulan dari setiap parameter tidak dibatasi oleh tipe data kolom, jadi kita bisa berakhir dengan sejumlah besar paket yang di-cache, tergantung pada literal numerik yang dikirimkan. Jumlah kombinasi dari numeric(1,0) ke numeric(38,38) sudah besar sebelum kita memikirkan beberapa parameter.

Parameterisasi Eksplisit

Masalah ini tidak muncul ketika kita menggunakan parameterisasi eksplisit, idealnya memilih tipe data yang sama dengan kolom yang dibandingkan dengan parameter:

ALTER DATABASE SCOPED CONFIGURATION 
    CLEAR PROCEDURE_CACHE;
GO
DECLARE 
    @stmt nvarchar(4000) =
        N'SELECT T.SomeValue FROM dbo.Test AS T WHERE T.SomeValue >= @P1 AND T.SomeValue < @P2;',
    @params nvarchar(4000) =
        N'@P1 numeric(19,8), @P2 numeric(19,8)';
 
EXECUTE sys.sp_executesql 
    @stmt, 
    @params, 
    @P1 = 987.65432, 
    @P2 = 123456.789;
 
EXECUTE sys.sp_executesql 
    @stmt, 
    @params, 
    @P1 = 98.76, 
    @P2 = 123.4567;
 
EXECUTE sys.sp_executesql 
    @stmt, 
    @params, 
    @P1 = 1.2, 
    @P2 = 1234.56789;

Dengan parameterisasi eksplisit, kueri cache paket hanya menampilkan satu paket yang di-cache, digunakan tiga kali, dan tidak memerlukan konversi jenis:

Parameterisasi eksplisit

Sebagai catatan akhir, saya menggunakan decimal dan numeric bergantian di bagian ini. Mereka secara teknis jenis yang berbeda, meskipun didokumentasikan sebagai sinonim dan berperilaku setara. Ini biasanya terjadi, tetapi tidak selalu:

-- Raises error 8120:
-- Column 'dbo.Test.SomeValue' is invalid in the select list
-- because it is not contained in either an aggregate function
-- or the GROUP BY clause.
SELECT CONVERT(decimal(19,8), T.SomeValue)
FROM dbo.Test AS T 
GROUP BY CONVERT(numeric(19,8), T.SomeValue);

Ini mungkin bug parser kecil, tetapi tetap berguna untuk konsisten (kecuali jika Anda sedang menulis artikel dan ingin menunjukkan pengecualian yang menarik).

Operator Aritmatika

Ada satu kasus edge lain yang ingin saya bahas, berdasarkan contoh yang diberikan dalam dokumentasi, tetapi dengan sedikit lebih detail (dan mungkin akurasi):

-- The dbo.LinkTypes table contains two rows
 
-- Uses simple parameterization
SELECT r = CONVERT(float, 1./ 7) 
FROM dbo.LinkTypes AS LT;
 
-- No simple parameterization due to
-- constant-constant comparison
SELECT r = CONVERT(float, 1./ 7) 
FROM dbo.LinkTypes AS LT 
WHERE 1 = 1;

Hasilnya berbeda, seperti yang didokumentasikan:

Hasil berbeda

Dengan Parameterisasi Sederhana

Ketika parameterisasi sederhana terjadi, SQL Server membuat parameter kedua nilai literal. 1. nilai diketik sebagai numeric(1,0) seperti yang diharapkan. Agak tidak konsisten, 7 diketik sebagai integer (bukan tinyint ). Aturan inferensi tipe telah dibangun dari waktu ke waktu, oleh tim yang berbeda. Perilaku dipertahankan untuk menghindari pelanggaran kode lama.

Langkah selanjutnya melibatkan / operator aritmatika. SQL Server membutuhkan tipe yang kompatibel sebelum melakukan pembagian. Diberikan numeric (decimal ) memiliki prioritas tipe data yang lebih tinggi daripada integer , integer akan dikonversi ke numeric .

SQL Server perlu secara implisit mengonversi integer ke numeric . Tapi presisi dan skala mana yang harus digunakan? Jawabannya dapat didasarkan pada literal asli, seperti yang dilakukan SQL Server dalam keadaan lain, tetapi selalu menggunakan numeric(10) di sini.

Tipe data hasil pembagian numeric(1,0) dengan numeric(10,0) ditentukan oleh lain seperangkat aturan, yang diberikan dalam dokumentasi untuk presisi, skala, dan panjang. Dengan memasukkan angka-angka ke dalam rumus untuk presisi hasil dan skala yang diberikan di sana, kita mendapatkan:

  • Presisi hasil:
    • p1 – s1 + s2 + maks(6, s1 + p2 + 1)
    • =1 – 0 + 0 + maks(6, 0 + 10 + 1)
    • =1 + maks(6, 11)
    • =1 + 11
    • =12
  • Skala hasil:
    • maks(6, s1 + p2 + 1)
    • =maks(6, 0 + 10 + 1)
    • =maks(6, 11)
    • =11

Tipe data 1. / 7 oleh karena itu, numeric(12, 11) . Nilai ini kemudian diubah menjadi float seperti yang diminta dan ditampilkan sebagai 0.14285714285 (dengan 11 digit setelah titik desimal).

Tanpa Parameterisasi Sederhana

Ketika parameterisasi sederhana tidak dilakukan, 1. literal diketik sebagai numeric(1,0) seperti sebelumnya. 7 awalnya diketik sebagai integer juga seperti yang terlihat sebelumnya. Perbedaan utamanya adalah integer diubah menjadi numeric(1,0) , jadi operator divisi memiliki tipe umum untuk digunakan. Ini adalah presisi dan skala terkecil yang dapat memuat nilai 7 . Ingat parameterisasi sederhana menggunakan numeric(10,0) di sini.

Rumus presisi dan skala untuk membagi numeric(1,0) dengan numeric(1,0) berikan tipe data hasil numeric(7,6) :

  • Presisi hasil:
    • p1 – s1 + s2 + maks(6, s1 + p2 + 1)
    • =1 – 0 + 0 + maks(6, 0 + 1 + 1)
    • =1 + maks(6, 2)
    • =1 + 6
    • =7
  • Skala hasil:
    • maks(6, s1 + p2 + 1)
    • =maks(6, 0 + 1 + 1)
    • =maks(6, 2)
    • =6

Setelah konversi terakhir menjadi float , hasil yang ditampilkan adalah 0.142857 (dengan enam digit setelah titik desimal).

Oleh karena itu, perbedaan yang diamati dalam hasil disebabkan oleh turunan tipe sementara (numeric(12,11) vs. numeric(7,6) ) daripada konversi akhir ke float .

Jika Anda memerlukan bukti lebih lanjut, konversi ke float tidak bertanggung jawab, pertimbangkan:

-- Simple parameterization
SELECT r = CONVERT(decimal(13,12), 1. / 7)
FROM dbo.LinkTypes AS LT;
 
-- No simple parameterization
SELECT r = CONVERT(decimal(13,12), 1. / 7)
FROM dbo.LinkTypes AS LT 
OPTION (MAXDOP 1);

Hasil dengan desimal

Hasilnya berbeda dalam nilai dan skala seperti sebelumnya.

Bagian ini tidak mencakup setiap kekhasan inferensi dan konversi tipe data dengan parameterisasi sederhana dengan cara apapun. Seperti yang dikatakan sebelumnya, Anda lebih baik menggunakan parameter eksplisit dengan tipe data yang diketahui jika memungkinkan.

Akhir Bagian 2

Bagian selanjutnya dari seri ini menjelaskan bagaimana parameterisasi sederhana mempengaruhi rencana eksekusi.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Python REST API Dengan Flask, Connexion, dan SQLAlchemy – Bagian 3

  2. Tindak lanjut pada opsi kursor

  3. Gejala Overhead Pengamat dan Jenis Tunggu

  4. Gaji tertinggi ke-n

  5. SCD Tipe 2