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

Seberapa mahalkah Konversi Implisit sisi kolom?

Pada bulan lalu saya telah terlibat dengan banyak pelanggan yang memiliki masalah konversi implisit sisi kolom yang terkait dengan beban kerja OLTP mereka. Pada dua kesempatan, efek akumulasi dari konversi implisit sisi kolom adalah penyebab yang mendasari masalah kinerja keseluruhan untuk SQL Server yang sedang ditinjau, dan sayangnya tidak ada pengaturan ajaib atau opsi konfigurasi yang dapat kami atur untuk memperbaiki situasi. ketika ini terjadi. Meskipun kami dapat menawarkan saran untuk memperbaiki buah lain yang menggantung lebih rendah yang mungkin memengaruhi kinerja secara keseluruhan, efek dari konversi implisit sisi kolom adalah sesuatu yang memerlukan perubahan desain skema untuk diperbaiki, atau perubahan kode untuk mencegah kolom- konversi sampingan terjadi terhadap skema database saat ini sepenuhnya.

Konversi implisit adalah hasil dari mesin database yang membandingkan nilai dari tipe data yang berbeda selama eksekusi kueri. Daftar konversi implisit yang mungkin terjadi di dalam mesin basis data dapat ditemukan di topik Buku Online Konversi Tipe Data (Mesin Basis Data). Konversi implisit selalu terjadi berdasarkan prioritas tipe data untuk tipe data yang dibandingkan selama operasi. Urutan prioritas tipe data dapat ditemukan di topik Buku Online Prioritas Tipe Data (Transact-SQL). Baru-baru ini saya membuat blog tentang konversi implisit yang menghasilkan pemindaian indeks, dan menyediakan bagan yang dapat digunakan untuk menentukan konversi implisit yang paling bermasalah juga.

Menyiapkan Tes

Untuk mendemonstrasikan overhead kinerja yang terkait dengan konversi implisit sisi kolom yang menghasilkan pemindaian indeks, saya telah menjalankan serangkaian pengujian berbeda terhadap database AdventureWorks2012 menggunakan tabel Sales.SalesOrderDetail untuk membuat tabel pengujian dan kumpulan data. Konversi implisit sisi kolom yang paling umum yang saya lihat sebagai konsultan terjadi ketika jenis kolom adalah char atau varchar, dan kode aplikasi melewati parameter yaitu nchar atau nvarchar dan memfilter pada kolom char atau varchar. Untuk mensimulasikan jenis skenario ini, saya membuat salinan tabel SalesOrderDetail (bernama SalesOrderDetail_ASCII) dan mengubah kolom CarrierTrackingNumber dari nvarchar menjadi varchar. Selain itu, saya menambahkan indeks nonclustered pada kolom CarrierTrackingNumber ke tabel SalesOrderDetail asli, serta tabel SalesOrderDetail_ASCII baru.

USE [AdventureWorks2012]
GO
-- Add CarrierTrackingNumber index to original Sales.SalesOrderDetail table
IF NOT EXISTS 
(
  SELECT 1 FROM sys.indexes 
    WHERE [object_id] = OBJECT_ID(N'Sales.SalesOrderDetail') 
    AND name=N'IX_SalesOrderDetail_CarrierTrackingNumber'
)
BEGIN
  CREATE INDEX IX_SalesOrderDetail_CarrierTrackingNumber 
    ON Sales.SalesOrderDetail (CarrierTrackingNumber);
END
GO
 
IF OBJECT_ID('Sales.SalesOrderDetail_ASCII') IS NOT NULL
BEGIN
  DROP TABLE Sales.SalesOrderDetail_ASCII;
END
GO
 
CREATE TABLE Sales.SalesOrderDetail_ASCII
(
  SalesOrderID int NOT NULL,
  SalesOrderDetailID int NOT NULL IDENTITY (1, 1),
  CarrierTrackingNumber varchar(25) NULL,
  OrderQty smallint NOT NULL,
  ProductID int NOT NULL,
  SpecialOfferID int NOT NULL,
  UnitPrice money NOT NULL,
  UnitPriceDiscount money NOT NULL,
  LineTotal  AS (isnull(([UnitPrice]*((1.0)-[UnitPriceDiscount]))*[OrderQty],(0.0))),
  rowguid uniqueidentifier NOT NULL ROWGUIDCOL,
  ModifiedDate datetime NOT NULL
);
GO
 
SET IDENTITY_INSERT Sales.SalesOrderDetail_ASCII ON;
GO
 
INSERT INTO Sales.SalesOrderDetail_ASCII
(
  SalesOrderID, SalesOrderDetailID, CarrierTrackingNumber, 
  OrderQty, ProductID, SpecialOfferID, UnitPrice, 
  UnitPriceDiscount, rowguid, ModifiedDate
)
SELECT 
  SalesOrderID, SalesOrderDetailID, CONVERT(varchar(25), CarrierTrackingNumber),  
  OrderQty, ProductID, SpecialOfferID, UnitPrice, 
  UnitPriceDiscount, rowguid, ModifiedDate 
FROM Sales.SalesOrderDetail WITH (HOLDLOCK TABLOCKX);
GO
 
SET IDENTITY_INSERT Sales.SalesOrderDetail_ASCII OFF;
GO
 
ALTER TABLE Sales.SalesOrderDetail_ASCII ADD CONSTRAINT
  PK_SalesOrderDetail_ASCII_SalesOrderID_SalesOrderDetailID 
  PRIMARY KEY CLUSTERED (SalesOrderID, SalesOrderDetailID);
 
CREATE UNIQUE NONCLUSTERED INDEX AK_SalesOrderDetail_ASCII_rowguid 
  ON Sales.SalesOrderDetail_ASCII (rowguid);
 
CREATE NONCLUSTERED INDEX IX_SalesOrderDetail_ASCII_ProductID 
  ON Sales.SalesOrderDetail_ASCII (ProductID);
 
CREATE INDEX IX_SalesOrderDetail_ASCII_CarrierTrackingNumber 
  ON Sales.SalesOrderDetail_ASCII (CarrierTrackingNumber);
GO

Tabel SalesOrderDetail_ASCII baru memiliki 121.317 baris dan berukuran 17,5 MB, dan akan digunakan untuk mengevaluasi overhead tabel kecil. Saya juga membuat tabel yang sepuluh kali lebih besar, menggunakan versi modifikasi dari skrip Enlarging the AdventureWorks Sample Databases dari blog saya, yang berisi 1.334.487 baris dan berukuran 190MB. Server uji untuk ini adalah VM 4 vCPU yang sama dengan RAM 4GB, menjalankan Windows Server 2008 R2 dan SQL Server 2012, dengan Paket Layanan 1 dan Pembaruan Kumulatif 3, yang telah saya gunakan di artikel sebelumnya, sehingga tabel akan muat sepenuhnya di memori , menghilangkan overhead I/O disk agar tidak memengaruhi pengujian yang dijalankan.

Beban kerja pengujian dibuat menggunakan serangkaian skrip PowerShell yang memilih daftar CarrierTrackingNumbers dari tabel SalesOrderDetail yang membangun ArrayList, dan kemudian secara acak memilih CarrierTrackingNumber dari ArrayList untuk menanyakan tabel SalesOrderDetail_ASCII menggunakan parameter varchar dan kemudian parameter nvarchar, dan kemudian untuk menanyakan tabel SalesOrderDetail menggunakan parameter nvarchar untuk memberikan perbandingan di mana kolom dan parameter keduanya adalah nvarchar. Masing-masing pengujian individual menjalankan pernyataan 10.000 kali untuk memungkinkan pengukuran overhead kinerja pada beban kerja yang berkelanjutan.

#No Implicit Conversions
$loop = 10000;
Write-Host "Small table no conversion start time:"
[DateTime]::Now
$query = @"SELECT * FROM Sales.SalesOrderDetail_ASCII "
          "WHERE CarrierTrackingNumber = @CTNumber;";
while($loop -gt 0)
{
  $Value = Get-Random -InputObject $Results;
  $SqlCmd = $SqlConn.CreateCommand();
  $SqlCmd.CommandText = $query;
  $SqlCmd.CommandType = [System.Data.CommandType]::Text;
 
  $SqlParameter = $SqlCmd.Parameters.AddWithValue("@CTNumber", $Value);
  $SqlParameter.SqlDbType = [System.Data.SqlDbType]::VarChar;
  $SqlParameter.Size = 30;
 
  $SqlCmd.ExecuteNonQuery() | Out-Null;
  $loop--;
}
Write-Host "Small table no conversion end time:"
[DateTime]::Now
 
Sleep -Seconds 10;
 
#Small table implicit conversions
$loop = 10000;
Write-Host "Small table implicit conversions start time:"
[DateTime]::Now
$query = @"SELECT * FROM Sales.SalesOrderDetail_ASCII "
          "WHERE CarrierTrackingNumber = @CTNumber;";
while($loop -gt 0)
{
  $Value = Get-Random -InputObject $Results;	
  $SqlCmd = $SqlConn.CreateCommand();
  $SqlCmd.CommandText = $query;
  $SqlCmd.CommandType = [System.Data.CommandType]::Text;
 
  $SqlParameter = $SqlCmd.Parameters.AddWithValue("@CTNumber", $Value);
  $SqlParameter.SqlDbType = [System.Data.SqlDbType]::NVarChar;
  $SqlParameter.Size = 30;
 
  $SqlCmd.ExecuteNonQuery() | Out-Null;
  $loop--;
}
Write-Host "Small table implicit conversions end time:"
[DateTime]::Now
 
Sleep -Seconds 10;
 
#Small table unicode no implicit conversions
$loop = 10000;
Write-Host "Small table unicode no implicit conversion start time:"
[DateTime]::Now
$query = @"SELECT * FROM Sales.SalesOrderDetail "
          "WHERE CarrierTrackingNumber = @CTNumber;"
while($loop -gt 0)
{
  $Value = Get-Random -InputObject $Results;	
  $SqlCmd = $SqlConn.CreateCommand();
  $SqlCmd.CommandText = $query;
  $SqlCmd.CommandType = [System.Data.CommandType]::Text;
 
  $SqlParameter = $SqlCmd.Parameters.AddWithValue("@CTNumber", $Value);
  $SqlParameter.SqlDbType = [System.Data.SqlDbType]::NVarChar;
  $SqlParameter.Size = 30;
 
  $SqlCmd.ExecuteNonQuery() | Out-Null;
  $loop--;
}
Write-Host "Small table unicode no implicit conversion end time:"
[DateTime]::Now

Serangkaian pengujian kedua dijalankan terhadap tabel SalesOrderDetailEnlarged_ASCII dan SalesOrderDetailEnlarged menggunakan parameterisasi yang sama seperti rangkaian pengujian pertama untuk menunjukkan perbedaan overhead karena ukuran data yang disimpan dalam tabel meningkat seiring waktu. Serangkaian pengujian terakhir juga dijalankan terhadap tabel SalesOrderDetail menggunakan kolom ProductID sebagai kolom filter dengan tipe parameter int, bigint, dan kemudian smallint untuk memberikan perbandingan overhead konversi implisit yang tidak menghasilkan pemindaian indeks untuk perbandingan.

Catatan:Semua skrip dilampirkan ke artikel ini untuk memungkinkan reproduksi tes konversi implisit untuk evaluasi dan perbandingan lebih lanjut.

Hasil Tes

Selama setiap eksekusi pengujian, Monitor kinerja dikonfigurasi untuk menjalankan Kumpulan Kolektor Data yang menyertakan waktu prosesor\% dan penghitung SQL Server:SQLStatisitics\Batch Requests/sec untuk melacak kinerja overhead untuk setiap pengujian. Selain itu, Peristiwa yang Diperpanjang telah dikonfigurasi untuk melacak peristiwa rpc_completed untuk memungkinkan pelacakan durasi rata-rata, cpu_time, dan pembacaan logis untuk setiap pengujian.

Hasil Nomor Pelacakan Operator Tabel Kecil


Gambar 1 – Grafik Monitor Kinerja penghitung

TestID Tipe Data Kolom Jenis Data Parameter % Waktu Prosesor Rata-rata Permintaan Batch Rata-rata/dtk Durasi j:mm:dd
1 Varchar Varchar 2,5 192.3 0:00:51
2 Varchar Nvarchar 19,4 46.7 0:03:33
3 Nvarchar Nvarchar 2.6 192.3 0:00:51

Tabel 2 – Rata-rata data Monitor Kinerja

Dari hasil, kita dapat melihat bahwa konversi implisit sisi kolom dari varchar ke nvarchar dan pemindaian indeks yang dihasilkan memiliki dampak yang signifikan terhadap kinerja beban kerja. Rata-rata % Waktu Prosesor untuk pengujian konversi implisit sisi kolom (TestID =2) hampir sepuluh kali lipat dari pengujian lain di mana konversi implisit sisi kolom, yang menghasilkan pemindaian indeks, tidak terjadi. Selain itu, rata-rata Permintaan Batch/dtk untuk pengujian konversi implisit sisi kolom hanya di bawah 25% dari pengujian lainnya. Durasi pengujian di mana konversi implisit tidak terjadi keduanya memakan waktu 51 detik, meskipun data disimpan sebagai nvarchar dalam pengujian nomor 3 menggunakan tipe data nvarchar, membutuhkan dua kali ruang penyimpanan. Hal ini diharapkan karena tabel masih lebih kecil dari buffer pool.

TestID Cpu_time rata-rata (s) Durasi rata-rata (µs) Logical_reads rata-rata
1 40.7 154.9 51.6
2 15,640.8 15,760.0 385.6
3 45.3 169.7 52,7

Tabel 3 – Rata-rata Peristiwa yang Diperpanjang

Data yang dikumpulkan oleh peristiwa rpc_completed di Peristiwa yang Diperpanjang menunjukkan bahwa rata-rata cpu_time, durasi, dan pembacaan logis yang terkait dengan kueri yang tidak melakukan konversi implisit sisi kolom secara kasar setara, di mana konversi implisit sisi kolom menimbulkan CPU yang signifikan overhead, serta durasi rata-rata yang lebih lama dengan pembacaan yang jauh lebih logis.

Hasil OperatorTrackingNumber Tabel Diperbesar


Gambar 4 – Grafik Monitor Kinerja penghitung

TestID Tipe Data Kolom Jenis Data Parameter % Waktu Prosesor Rata-rata Permintaan Batch Rata-rata/dtk Durasi j:mm:dd
1 Varchar Varchar 7.2 164.0 0:01:00
2 Varchar Nvarchar 83,8 15.4 0:10:49
3 Nvarchar Nvarchar 7.0 166.7 0:01:00

Tabel 5 – Rata-rata data Monitor Kinerja

Saat ukuran data meningkat, overhead kinerja konversi implisit sisi kolom juga meningkat. Rata-rata % Waktu Prosesor untuk pengujian konversi implisit sisi kolom (TestID =2), sekali lagi, hampir sepuluh kali lipat dari pengujian lain di mana konversi implisit sisi kolom yang menghasilkan pemindaian indeks, tidak terjadi. Selain itu, Permintaan Batch rata-rata/dtk untuk pengujian konversi implisit sisi kolom hanya di bawah 10% dari pengujian lainnya. Durasi pengujian di mana konversi implisit tidak terjadi membutuhkan waktu satu menit, sedangkan pengujian konversi implisit sisi kolom membutuhkan waktu hampir sebelas menit untuk dijalankan.

TestID Cpu_time rata-rata (s) Durasi rata-rata (µs) Logical_reads rata-rata
1 728.5 1.036.5 569.6
2 214.174,6 59,519.1 4,358.2
3 821.5 1.032.4 553.5

Tabel 6 – Rata-rata Peristiwa yang Diperpanjang

Hasil Peristiwa yang Diperpanjang benar-benar mulai menunjukkan overhead kinerja yang disebabkan oleh konversi implisit sisi kolom untuk beban kerja. Rata-rata cpu_time per eksekusi melompat ke lebih dari 214 md dan lebih dari 200 kali cpu_time untuk pernyataan yang tidak memiliki konversi implisit sisi kolom. Durasinya juga hampir 60 kali lipat dari pernyataan yang tidak memiliki konversi implisit sisi kolom.

Ringkasan

Karena ukuran data terus meningkat, overhead yang terkait dengan konversi implisit sisi kolom yang menghasilkan pemindaian indeks untuk beban kerja juga akan terus bertambah, dan yang penting untuk diingat adalah bahwa pada titik tertentu, tidak ada jumlah perangkat keras akan mampu mengatasi overhead kinerja. Konversi implisit adalah hal yang mudah untuk dicegah ketika ada desain skema database yang baik, dan pengembang mengikuti teknik pengkodean aplikasi yang baik. Dalam situasi di mana praktik pengkodean aplikasi menghasilkan parameterisasi yang memanfaatkan parameterisasi nvarchar, lebih baik mencocokkan desain skema database dengan parameterisasi kueri daripada menggunakan kolom varchar dalam desain database dan menimbulkan overhead kinerja dari konversi implisit sisi kolom.

Unduh skrip demo:Implicit_Conversion_Tests.zip (5 KB)


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Memulai Menyesuaikan Kinerja di Azure SQL Database

  2. Memperbaiki Kehilangan Data Menggunakan Pengiriman Log dengan Pemulihan Tertunda

  3. Bagaimana Rencana Paralel Memulai – Bagian 4

  4. Baca Tingkat Isolasi Tanpa Komitmen

  5. Cara Memfilter Baris tanpa NULL di kolom