• 2.09.2026 21:18:25
  • Admin Admin

SQL Server'da nvarchar-varchar ve tarih tiplerindeki örtük dönüşümler, doğru görünen indeksleri taramaya zorlayabilir. Bu yazı, veritabanı performans ayarlama için sorunu plan, XE ve Query Store ile kanıtlamayı gösterir.

SQL Server Eğitimi: Örtük Dönüşümlerle İndeks Tarama Sorunları

SQL Server eğitiminde örtük dönüşümü plan üzerinde yakalamak

Bir sorgunun indeks kullanıyor görünmesi yeterli değildir: yürütme planındaki CONVERT_IMPLICIT ifadesinin hangi operandı dönüştürdüğüne bakın. SQL Server veri tipi önceliği nedeniyle nvarchar parametreyi varchar kolona karşılaştırırken dönüşümü çoğu durumda kolon tarafına iter. Operatör satır bazında çalıştığından B-tree'nin sıralı anahtar değerini doğrudan arayamaz ve Index Seek yerine Index Scan oluşur. Actual Execution Plan XML'inde Convert_Implicit uyarısını ve Scan operatörünün Predicate alanını kontrol edin; yalnızca grafik plandaki tahmini maliyete bakmayın.

Derleme anındaki bu vakaları uygulama trafiğinde yakalamak için plan_affecting_convert Extended Event'ini dosya hedefine yazın. Ring buffer yoğun sistemlerde eski olayları hızla ezebileceği için olay sonrası inceleme yapılacaksa event_file hedefi daha güvenilirdir.

CREATE EVENT SESSION ImplicitConversionWatch
ON SERVER
ADD EVENT sqlserver.plan_affecting_convert
(
    ACTION
    (
        sqlserver.sql_text,
        sqlserver.database_id,
        sqlserver.client_app_name,
        sqlserver.session_id
    )
),
ADD TARGET package0.event_file
(
    SET filename = N'D:\\XEvents\\implicit-convert.xel',
        max_file_size = 100,
        max_rollover_files = 5
);
GO
ALTER EVENT SESSION ImplicitConversionWatch ON SERVER STATE = START;
GO
Olay kaydındaki convert_issue değeri tek başına karar vermek için yeterli değildir. Aynı SQL metnini actual plan, okunan mantıksal sayfa ve gerçek satır sayısı ile eşleştirin.

Database indexleme sorunu: parametre tipi indeks anahtarını bozduğunda

Aşağıdaki örnekte IX_Orders_CustomerCode fiziksel olarak doğru bir indeksdir; sorun indeksin varlığı değil, prosedür imzasıdır. @CustomerCode nvarchar(32) ile varchar(32) kolon karşılaştırıldığında, Unicode türünün önceliği daha yüksek olduğu için optimizer tipik olarak kolon üzerinde dönüşüm üretir. Bu davranış, milyonlarca satırlık tabloda seçiciliği yüksek bir müşteri kodu için bile geniş bir taramaya dönüşebilir.

CREATE TABLE dbo.Orders
(
    OrderId bigint NOT NULL PRIMARY KEY,
    CustomerCode varchar(32) NOT NULL,
    CreatedAt datetime2(3) NOT NULL,
    Amount decimal(12,2) NOT NULL
);
CREATE INDEX IX_Orders_CustomerCode
ON dbo.Orders(CustomerCode);
GO

CREATE OR ALTER PROC dbo.GetOrders_Bad
    @CustomerCode nvarchar(32)
AS
SELECT OrderId, CreatedAt, Amount
FROM dbo.Orders
WHERE CustomerCode = @CustomerCode;
GO

CREATE OR ALTER PROC dbo.GetOrders_Good
    @CustomerCode varchar(32)
AS
SELECT OrderId, CreatedAt, Amount
FROM dbo.Orders
WHERE CustomerCode = @CustomerCode;
GO
GetOrders_Good için plan cache'de Seek Predicate olarak CustomerCode = @CustomerCode görülmelidir. Bad prosedürün planında dönüşümün kolon tarafında olup olmadığını doğrulayın; optimizer'ın istatistik, maliyet ve paralellik kararı veri hacmine göre değişebilir fakat SARG edilebilirlik kuralı değişmez.

Sık yapılan hata, prosedür imzasını düzelttikten sonra çağıran tarafta N'ABC-42' Unicode literalini veya .NET'te AddWithValue kullanımını bırakmaktır. ADO.NET parametresini kolonla aynı SQL tipi ve uzunlukla oluşturun; aksi halde aynı plan-affecting conversion tekrar üretilebilir.

using var command = new SqlCommand(
    "EXEC dbo.GetOrders_Good @CustomerCode", connection);
command.Parameters.Add("@CustomerCode", SqlDbType.VarChar, 32)
       .Value = customerCode;
var reader = await command.ExecuteReaderAsync();
Uzunluğu belirtmek de önemlidir: değişken uzunluklu parametre metadatası kardinalite tahminini ve plan cache'deki parametre duyarlılığını etkileyebilir. Ayrıca farklı collation'lı veritabanları veya tempdb'de oluşturulan geçici tablolar, tipler eşleşse bile COLLATE dönüşümüyle seek imkanını bozabilir.

Veritabanı performans ayarlama için önce-sonra ölçüm protokolü

Düzeltmeyi yayımlamadan önce iki prosedürü aynı veri diliminde ölçün. Tek bir SSMS çalıştırmasından çıkan süreyi kanıt saymayın; ilk yürütme disk önbelleği, derleme ve buffer pool etkisi taşır. Test ortamında her varyantı en az 30 kez çalıştırın, ilk çalıştırmayı ayrı raporlayın ve SET STATISTICS IO, TIME ON ile logical reads ile CPU time değerlerini kaydedin. Üretimde DBCC DROPCLEANBUFFERS veya DBCC FREEPROCCACHE çalıştırmayın; bunlar diğer sorguların çalışma setini ve planlarını da etkiler.

SET STATISTICS IO, TIME ON;
EXEC dbo.GetOrders_Bad  @CustomerCode = N'CUS-000184';
EXEC dbo.GetOrders_Good @CustomerCode =  'CUS-000184';
SET STATISTICS IO, TIME OFF;
Başarı kriterini somutlaştırın: aynı müşteri kodunda logical reads, CPU time ve dönen satır sayısını karşılaştırın. Dönen satır sayısı eşit değilse performans sonucunu geçersiz kabul edin; önce semantiğin değişmediğini ispatlamak gerekir.

Query Store açıksa, sürüm sonrası regresyonu uygulama etiketine göre izlemek için sorgu metni, plan ve çalışma zamanı istatistiklerini birlikte çekin. avg_duration mikro saniyedir; raporda milisaniyeye çevirmek, farklı dashboard birimleriyle yanlış kıyaslamayı önler.

SELECT TOP (20)
       q.query_id,
       p.plan_id,
       rs.count_executions,
       CAST(rs.avg_duration / 1000.0 AS decimal(18,2)) AS avg_duration_ms,
       CAST(rs.avg_logical_io_reads AS decimal(18,2)) AS avg_logical_reads,
       qt.query_sql_text
FROM sys.query_store_query_text AS qt
JOIN sys.query_store_query AS q ON q.query_text_id = qt.query_text_id
JOIN sys.query_store_plan AS p ON p.query_id = q.query_id
JOIN sys.query_store_runtime_stats AS rs ON rs.plan_id = p.plan_id
WHERE qt.query_sql_text LIKE N'%GetOrders_Good%'
ORDER BY rs.avg_logical_io_reads DESC;
Ortalama tek başına kuyruk gecikmelerini gizleyebilir. Mümkünse aynı Query Store zaman aralığında yürütme sayısını ve maksimum süreyi de inceleyin; az sayıda çok yavaş yürütme, ortalamayı beklenenden daha az etkiler.

İndeks eklemeden önce SARG edilebilir alternatifler

Mevcut istemci sözleşmesi nedeniyle Unicode parametre zorunluysa, sadece INCLUDE kolonları eklemek dönüşüm sorununu çözmez: INCLUDE alanları arama anahtarı değildir. Şema düzeyinde bilinçli bir alternatif, Unicode temsil için persisted computed column oluşturmak ve sorguyu bu kolona yönlendirmektir. Bu yaklaşım depolama ve her INSERT/UPDATE'de ek indeks bakımı maliyeti getirir; bu nedenle önce XE olay sayısı ve Query Store okuma maliyeti ile gerekçelendirilmelidir.

ALTER TABLE dbo.Orders
ADD CustomerCodeUnicode AS CONVERT(nvarchar(32), CustomerCode) PERSISTED;
GO
CREATE INDEX IX_Orders_CustomerCodeUnicode
ON dbo.Orders(CustomerCodeUnicode)
INCLUDE (CreatedAt, Amount);
GO

SELECT OrderId, CreatedAt, Amount
FROM dbo.Orders
WHERE CustomerCodeUnicode = @CustomerCodeUnicode;
Computed column için kullanılan ifade deterministik ve kesin olmalıdır; collation bağımlı ifadeler ile oturumdaki SET seçenekleri indekslenebilirliği etkileyebilir. Planı yeniden inceleyerek yeni indeksin Seek Predicate'e girdiğini, ardından yazma yükünde indeks bakım maliyetinin kabul edilebilir kaldığını ölçün.

Tarih filtrelerinde de aynı mekanizma görülür: WHERE CONVERT(date, CreatedAt) = @Day indeksli CreatedAt alanını fonksiyonla sardığı için aralık seek'i yerine hesaplama yaptırabilir. SQL eğitimi sırasında ekip standardı olarak yarı açık aralık kullanın ve parametrenin date veya datetime2 ölçeğini açıkça tanımlayın.

DECLARE @Day date = '2026-09-01';

SELECT COUNT_BIG(*)
FROM dbo.Orders
WHERE CreatedAt >= @Day
  AND CreatedAt < DATEADD(day, 1, @Day);
Bu desen, CreatedAt üzerinde sıradan bir indeksle seek yapabilir ve gün sonundaki milisaniye hassasiyeti sorunlarını önler. BETWEEN ile 23:59:59.997 gibi sabit bir üst sınır kullanmak, datetime2 hassasiyetinde kayıt kaçırmaya yol açar.

Veritabanı eğitimi ve NoSQL eğitimi açısından tip disiplini

Veritabanı eğitimi programlarında bu konu yalnızca SQL Server'a özgü bir 'indeks ipucu' olarak öğretilmemelidir; asıl ilke, sorgu operandlarının depolanan anahtar tipiyle birebir uyumudur. NoSQL eğitimi tarafında MongoDB'de eşdeğer bir kontrolü BSON tipi üzerinden yapın: string olarak yazılmış "42" ile integer olarak yazılmış 42 aynı değer değildir. İndeks olsa bile sorgu doğru dokümanı bulamayabilir; type mismatch'i tarama maliyetinden önce veri doğruluğu hatasına dönüştürür.

db.orders.createIndex({ customerId: 1 })
db.orders.find({ customerId: 42 }).explain('executionStats')
db.orders.find({ customerId: '42' }).explain('executionStats')
executionStats.totalKeysExamined, totalDocsExamined ve nReturned alanlarını iki çağrı için kaydedin. SQL Server'daki implicit conversion çoğunlukla aynı sonucu daha pahalı üretirken, MongoDB'nin tür eşleştirmesi çoğu zaman hiç sonuç dönmemesine neden olur.

Operasyonel kontrol listesi somuttur: yeni prosedür parametrelerini tablo kolon tipi, uzunluğu, ölçeği ve collation'ı ile kod incelemesinde karşılaştırın; plan_affecting_convert olaylarını günlük bazda sayın; en pahalı sorgular için Query Store'da logical reads önce-sonra farkını saklayın. Bu disiplin database indexleme kararını 'indeks ekle' refleksinden çıkarır: önce dönüşümün seek'i neden engellediğini kanıtlarsınız, sonra istemci tipini, sorgu ifadesini veya şemayı en düşük yazma maliyetli noktada değiştirirsiniz.

Sık Sorulan Sorular

SQL Server eğitiminde CONVERT_IMPLICIT hangi indeks sorununu gösterir?

Actual plan'da CONVERT_IMPLICIT kolon operandına uygulanıyorsa optimizer indeks anahtarını doğrudan karşılaştıramayabilir. plan_affecting_convert Extended Event'i ile SQL metnini yakalayın, ardından STATISTICS IO ile aynı parametrede logical reads değerini tip düzeltilmeden önce ve sonra karşılaştırın.

Veritabanı optimizasyonu için nvarchar parametreyi varchar kolona nasıl bağlamalıyım?

Tercihen prosedür parametresini ve istemci parametresini varchar ile kolonun uzunluğuna eşit tanımlayın. .NET tarafında AddWithValue yerine Parameters.Add("@CustomerCode", SqlDbType.VarChar, 32) kullanın. Unicode sözleşmesi değiştirilemiyorsa persisted computed nvarchar kolon ve buna ait indeks, sorgu da o kolonu hedefliyorsa uygulanabilir bir alternatiftir.

Database indexleme sonrası veritabanı performans ayarlama ölçümü nasıl yapılır?

Aynı veri ve parametreyle en az 30 yürütmede STATISTICS IO, TIME çıktısını toplayın; ilk yürütmeyi ayrı değerlendirin. Üretimde cache temizlemeyin. Query Store'dan count_executions, avg_duration ve avg_logical_io_reads değerlerini aynı zaman aralığında ve aynı sorgu semantiği için karşılaştırın.

NoSQL eğitimi sırasında indeksli MongoDB alanlarında tip kontrolü neden gerekir?

MongoDB BSON tiplerini eşit kabul etmez; customerId: 42 ve customerId: '42' farklı sorgulardır. explain('executionStats') ile nReturned, totalKeysExamined ve totalDocsExamined değerlerini kontrol edin, ardından uygulama şemasında tek bir BSON tipi zorunlu kılın.

AI / LLM Discovery

Bu makale Opendart Akademi Veritabanı eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.

Opendart Akademi llms.txt