SQL Server 2025 Native JSON Data Type – nvarchar(max)’i Emekliye Ayırmanın Vakti


1. Başlangıç

Geçen hafta SQL Server 2025 Optimized Locking üzerine derinlemesine bir yazı çıkarmıştık. Yorumlardan ve LinkedIn mesajlarından gördüğüm kadarıyla en çok ilgi çeken kısım LAQ’ın iş kuralı semantiğine etkisi oldu, kendi ortamınızda denedikçe gerçekten ne kadar kritik bir detay olduğunu görüyorsunuz. Bu hafta konumuz biraz farklı bir cephede: Native JSON Data Type.

SQL Server 2016’dan beri JSON’ı nvarchar(max) kolonlarda saklayıp JSON_VALUE, JSON_QUERY, JSON_MODIFY gibi fonksiyonlarla işliyorduk. Bu yaklaşım ‘çalışan’ bir çözümdü ama ‘iyi’ değildi: her okumada metni baştan parse etmek, her yazımda dokümanın tamamını yeniden yazmak, depolama tarafında verimsiz kalmak gibi maliyetler vardı. SQL Server 2025 bu tabloyu temelden değiştiren bir json tipi getiriyor. Azure SQL Database tarafında zaten GA, on-prem’de şu an preview, ama feature seti tamamen prod’a hazır görünüyor.

Yazı boyunca dört workshop yapacağız. Workshop 1’de mevcut nvarchar(max) bir tabloyu yeni json tipine taşıyacağız, storage farkını ölçeceğiz. Workshop 2’de .modify() metodunu klasik JSON_MODIFY ile karşılaştırıp neden in-place update’in oyun değiştirici olduğunu göreceğiz. Workshop 3’te JSON_OBJECTAGG ve JSON_ARRAYAGG ile T-SQL’den temiz REST API response’u üreteceğiz. Workshop 4’te ise yeni CREATE JSON INDEX ile JSON_VALUE, JSON_PATH_EXISTS ve JSON_CONTAINS predicate’lerinin filtreleme performansını ölçeceğiz.

Yazıdaki örneklerin tamamını SQL Server 2025 (17.x) preview kurulumumda çalıştırdım. PREVIEW_FEATURES database scoped configuration’ı açık olmak zorunda; aksi halde JSON INDEX, .modify() ve JSON_CONTAINS bekleyen kapıların ardında kalır. Önce o ayarı halledeceğiz, sonra senaryolara geçeceğiz.

2. Eski Dünya nvarchar(max) ile JSON Saklamak

Şu güne kadar pratiğimizde JSON saklamak için iki seçenek vardı: nvarchar(max) kolon veya parsed columns (JSON_VALUE ile çıkarılmış değerleri ayrı kolonlara persist etmek). Birincisi esnek ama verimsiz, ikincisi performanslı ama esnekliği yok eder. Çoğu uygulamada birincisi tercih edilirdi:

CREATE TABLE dbo.WebSiteLogs_Old
(
log_id BIGINT IDENTITY PRIMARY KEY,
log_data NVARCHAR(MAX)
CONSTRAINT chk_log_isjson CHECK (ISJSON(log_data) = 1)
);

Bu yaklaşımın üç temel sorunu vardı. Birincisi, her okuma için JSON parse maliyeti: JSON_VALUE veya JSON_QUERY çağırdığınız her satırda engine, metnin tamamını parse etmek zorundadır. 5 KB’lık bir log dokümanı, milyonlarca satır üzerinde, ciddi CPU yiyebilir. İkincisi, yazma maliyeti: tek bir property güncelleyeceksiniz diye JSON_MODIFY çağırınca engine yeni metni baştan üretir, eski varchar string yerine yenisini koyar, in-place yazma yapamaz. Üçüncüsü, depolama: text formatı sıkıştırma için optimize değil, ek olarak her property adı tekrar tekrar saklanır.

Buna rağmen nvarchar(max) tarafında biriktirdiğimiz kod hiç boşa gitmiyor, yeni json tipi tüm bu fonksiyonları (JSON_VALUE, JSON_QUERY, JSON_MODIFY, OPENJSON, ISJSON, JSON_PATH_EXISTS) aynen kabul ediyor. Yani uygulama kodunu değiştirmeden sadece kolon tipini değiştirebilirsiniz. Bu, geçişin önündeki en büyük psikolojik engeli kaldırıyor.

3. Yeni Dünya Native JSON Data Type

SQL Server 2025’te eklenen json tipi, JSON dokümanlarını native binary format’ta saklıyor. Microsoft bu format için özel bir isim duyurmadı ama davranışı özetlersek: doküman yazılırken bir kez parse edilir, sonraki tüm okumalar parsed haldeki binary üzerinden yapılır. Property erişimleri direkt offset üzerinden, parse cycle’ı yok.

-- Yeni json tipiyle tablo
CREATE TABLE dbo.WebSiteLogs
(
log_id BIGINT IDENTITY PRIMARY KEY,
log_data JSON NOT NULL
);
-- INSERT'lerde otomatik validasyon — geçersiz JSON girersen
-- INSERT failed olur, ek CHECK constraint gerekmiyor
INSERT INTO dbo.WebSiteLogs (log_data)
VALUES ('{"event":"login","user":"yavuz","ts":"2026-05-05T08:30:00Z"}');

İki kazanım hemen göze çarpıyor: ISJSON() check constraint’ine ihtiyaç yok (engine INSERT/UPDATE’te otomatik validate ediyor) ve uygulama kodunda hiç değişiklik gerekmedi. Önceki tablodaki JSON_VALUE() çağrılarınızı yeni tablodaki json kolon üzerinde aynen kullanabilirsiniz.

Storage tarafına gelirsek, json tipinin internal kodlaması UTF-8 (Latin1_General_100_BIN2_UTF8 collation). Bu hem Avrupa karakterlerini hem Asya’yı verimli saklayan bir kombinasyon. Sıkıştırma için optimize edilmiş bir format kullanıyor, testlerimde aynı doküman nvarchar(max) ve json kolonlarında karşılaştırıldığında %20-35 arası storage kazancı gördüm, dokümanın yapısına göre değişiyor. Tekrar eden property adlarını dictionary olarak tutuyor olabilir, ama Microsoft internal format detayını açıklamadı.

4. Storage ve I/O – Binary Format Avantajı

Aşağıdaki diyagram, aynı update işleminin nvarchar(max) ve json kolonu üzerindeki I/O karakteristiğini gösteriyor. Eski yöntemde her property güncellemesi ‘oku → parse → değiştir → serialize → yaz’ adımlarından geçer. Yeni json tipinde sayfa üzerinde doğrudan offset hesaplanır, kısa string’ler için in-place yazılır.

nvarchar(max) JSON_MODIFY tüm dokümanı yeniden üretirken, json kolon üzerinde .modify() kısa stringleri in-place günceller, ek I/O yok.

Tüm kazanımlar tabii ki sihir değil. json tipinde bir kolon için bazı sınırlar var:

  • Tek doküman maksimum 2 GB.
  • Tek doküman içinde maksimum 32K unique key.
  • Bir property anahtarı (key) maksimum 7998 byte.
  • Bir string değer maksimum 536.870.911 byte (yaklaşık 512 MB).
  • Bir objedeki maksimum property sayısı: 65535.
  • Bir array’deki maksimum eleman sayısı: 65535.
  • Maksimum nesting level: 128.

Gerçek dünyada 32K unique key veya 128 nesting level senaryosuyla ben hiç karşılaşmadım, ama API gateway’lerden gelen response’larda 65535 array eleman sınırı bazen sıkıntı olabiliyor. Veriyi taşırken bu limitleri kontrol etmeyi unutmayın.

5. .modify() Method In-Place JSON Update

Geleneksel JSON_MODIFY fonksiyonu, kolonun değerini fonksiyonun döndürdüğü yeni metinle değiştiren bir SET ifadesidir. Yani semantik olarak ‘tüm dokümanı yeniden yaz’ demektir. json tipinde yeni gelen .modify() metodu farklı çalışıyor: doğrudan dokümanın iç yapısı üzerinde, in-place modifikasyon yapabiliyor (mümkün olduğu durumlarda).

Mümkünlük şartı şöyle: yeni değer eski değerden küçük veya eşitse JSON string için in-place yazım yapılır. JSON sayı değerleri için aynı tipteyse veya range içindeyse in-place yazım gerçekleşir. Aksi durumda doküman yeniden organize edilir ama yine de JSON_MODIFY’dan daha verimli, çünkü tüm metni parse etmesi gerekmez.

.modify() metodu önce in-place yazılabilir mi diye kontrol ediyor; kısaltma veya aynı tip durumunda in-place, aksi halde restructure path.

-- .modify() basit kullanım
DROP TABLE IF EXISTS dbo.JsonTable;
CREATE TABLE dbo.JsonTable
(
id INT PRIMARY KEY,
d JSON
);
INSERT INTO dbo.JsonTable (id, d)
VALUES (1, '{"a":1, "b":"abc", "c":true}');
-- a property'sini güncelle (sayı, in-place)
UPDATE dbo.JsonTable
SET d.modify('$.a', 14859)
WHERE id = 1;
-- b property'sini güncelle (string, eski 'abc' → yeni 'def', aynı uzunluk → in-place)
UPDATE dbo.JsonTable
SET d.modify('$.b', 'def')
WHERE id = 1;
SELECT d FROM dbo.JsonTable WHERE id = 1;
-- {"a":14859,"b":"def","c":true}

Workshop 2’de bunun JSON_MODIFY ile ölçülebilir performans farkını göreceğiz. Spoiler: yüz binlerce satırlık update batch’lerinde fark dramatik. Production’da gerçek bir log tablosu üzerinde test etmenizi öneririm.

6. JSON_OBJECTAGG ve JSON_ARRAYAGG Aggregation’dan JSON’a

T-SQL tarafında ‘satırları nasıl JSON yaparım’ sorusunun klasik cevabı FOR JSON PATH idi. İyi çalışıyordu ama satırı tek tek toplayan, GROUP BY ile birleşen senaryolarda tipik olarak STRING_AGG + JSON_VALUE kombinasyonlarına başvurmak gerekiyordu, biraz kabaydı. SQL Server 2025’te ANSI standartlı iki yeni aggregation fonksiyonu geldi: JSON_OBJECTAGG ve JSON_ARRAYAGG.

Bu iki fonksiyon, Oracle ve PostgreSQL kullanmış ekipler için hızla tanıdık gelecek. Söz dizimi temiz, GROUP BY ile uyumlu, NULL davranışını ANSI null clause ile kontrol edebiliyorsunuz, ORDER BY ile array elementlerini sıralayabiliyorsunuz.

-- JSON_ARRAYAGG temel kullanım
SELECT JSON_ARRAYAGG(c1)
FROM (VALUES ('c'), ('b'), ('a')) AS t(c1);
-- ["c","b","a"]
-- ORDER BY ile sıralı
SELECT JSON_ARRAYAGG(c1 ORDER BY c1)
FROM (VALUES ('c'), ('b'), ('a')) AS t(c1);
-- ["a","b","c"]
-- GROUP BY ile gruba göre array
SELECT TOP (5)
c.object_id,
JSON_ARRAYAGG(c.name ORDER BY c.column_id) AS column_list
FROM sys.columns AS c
GROUP BY c.object_id;

JSON_OBJECTAGG, key/value çiftlerinden JSON object inşa eder. NULL davranışı için iki seçenek var: NULL ON NULL (NULL’ları null literal olarak yaz) veya ABSENT ON NULL (NULL’ları olmamış say). Default ABSENT ON NULL — yani NULL property otomatik düşer. RETURNING JSON ile çıktının native json tipinde dönmesini sağlayabilirsiniz; aksi halde default çıktı nvarchar(max).

-- JSON_OBJECTAGG — kullanıcı setting'lerini object olarak topla
DECLARE @t TABLE (user_id INT, setting_key NVARCHAR(50), setting_val NVARCHAR(200));
INSERT INTO @t VALUES
(1, 'theme', 'dark'),
(1, 'language', 'tr'),
(1, 'timezone', 'Europe/Istanbul'),
(2, 'theme', 'light'),
(2, 'language', 'en');
SELECT
user_id,
JSON_OBJECTAGG(setting_key : setting_val ABSENT ON NULL RETURNING JSON)
AS settings
FROM @t
GROUP BY user_id;
-- 1 | {"theme":"dark","language":"tr","timezone":"Europe/Istanbul"}
-- 2 | {"theme":"light","language":"en"}

Bunu STRING_AGG ile yapmaya çalışan kodlardan kurtulmak yalnızca okunabilirlik kazancı değil; engine içinde de daha verimli execution plan üretiyor, son testimde 200 bin satırlık dataset üzerinde JSON_OBJECTAGG, eşdeğer STRING_AGG’tan ortalama %18 hızlıydı. Production raporlama view’ları için cazip.

7. CREATE JSON INDEX — Filter Performansını Yükseltmek

JSON sorgularında uzun süredir kullandığımız hile, JSON_VALUE çağrısını computed column olarak persist edip üzerine standart B-tree index koymaktı. Çalışıyordu ama her property için ayrı computed column + index demekti, schema’yı bulandırıyordu.

SQL Server 2025’te yeni bir DDL geliyor: CREATE JSON INDEX. Tek komutla bir json kolon üzerine generic JSON index’i oluşturuyor. JSON_VALUE, JSON_PATH_EXISTS ve JSON_CONTAINS predicate’lerinden gelen erişimleri optimize ediyor.

-- Mevcut SalesOrderHeader tablosuna json kolonu eklenmiş varsayalım
ALTER TABLE Sales.SalesOrderHeader
ADD Info JSON NULL;
-- JSON Index oluştur
CREATE JSON INDEX sales_info_idx
ON Sales.SalesOrderHeader (Info);
-- Bu sorgu artık index'ten faydalanır
SELECT COUNT(*)
FROM Sales.SalesOrderHeader
WHERE JSON_VALUE(Info, '$.Customer.Type') = 'IN';

İki önemli detay:
(1) JSON Index için tablo clustered primary key’e sahip olmalı, indexed view üzerinde çalışmıyor.
(2) Şu sürümde online build desteği yok — ONLINE = ON ile vermeye kalkarsanız hata alırsınız. Büyük tablolarda bakım penceresinde kuracaksınız.

Ek olarak OPTIMIZE_FOR_ARRAY_SEARCH = ON option’ı var; JSON dokümanlarınız büyük array’ler içeriyorsa ve içlerinde arama yapıyorsanız bu option array tarafını optimize ediyor. Default OFF. Açtığınızda sys.json_indexes view’ından kontrol edebilirsiniz:

-- Array search optimizasyonlu JSON index
CREATE JSON INDEX sales_info_idx_arr
ON Sales.SalesOrderHeader (Info)
WITH (OPTIMIZE_FOR_ARRAY_SEARCH = ON);
-- Index'lerin durumunu görüntüle
SELECT
i.name,
OBJECT_NAME(i.object_id) AS table_name,
ji.optimize_for_array_search
FROM sys.json_indexes ji
JOIN sys.indexes i
ON ji.object_id = i.object_id
AND ji.index_id = i.index_id;

JSON index’in kapsamadığı operatörler şu an için: LIKE, IS [NOT] NULL. Bunlarda hâlâ computed column + standart index hilesine gerek var. Workshop 4’te bu durumların net performans farkını ölçeceğiz.

8. Workshop 1 – nvarchar(max)’tan json’a Migration

Şu senaryoyu kuralım: WebSiteLogs adında, üç ay boyunca milyonlarca log satırı biriktiren bir tablomuz var. Mevcut yapı nvarchar(max). Yeni json tipine taşımak istiyoruz. Migration yaklaşımı, downtime’a izin verip vermediğinize göre değişir.

Önce preview feature’ı açalım — gerek olacak:

-- Preview features (.modify, JSON_CONTAINS, JSON Index için)
ALTER DATABASE SCOPED CONFIGURATION
SET PREVIEW_FEATURES = ON;
GO

Eski tablo:

CREATE TABLE dbo.WebSiteLogs_Old
(
log_id BIGINT IDENTITY PRIMARY KEY,
log_data NVARCHAR(MAX)
CONSTRAINT chk_log_isjson CHECK (ISJSON(log_data) = 1)
);
INSERT INTO dbo.WebSiteLogs_Old (log_data)
SELECT TOP (200000)
CONCAT(
'{"event":"login","user":"user_',
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),
'","ts":"2026-05-05T08:30:00Z","meta":{"ip":"10.0.0.1","agent":"Mozilla"}}'
)
FROM sys.all_columns a CROSS JOIN sys.all_columns b;

Hedef tablo:

CREATE TABLE dbo.WebSiteLogs
(
log_id BIGINT PRIMARY KEY,
log_data JSON NOT NULL
);
-- Aktarım — CAST ile nvarchar(max) → json otomatik
INSERT INTO dbo.WebSiteLogs (log_id, log_data)
SELECT log_id, CAST(log_data AS JSON)
FROM dbo.WebSiteLogs_Old;
-- Storage farkını ölç
EXEC sp_spaceused 'dbo.WebSiteLogs_Old';
EXEC sp_spaceused 'dbo.WebSiteLogs';

Benim test ortamımda 200 bin satırlık örnek için sp_spaceused sonucu yaklaşık şuydu: nvarchar(max) tablosu 78 MB, json tablosu 56 MB. %28’lik bir storage kazancı. Sıkıştırma açıkken farkın daha da büyüdüğünü gözlemledim, page compression nvarchar tarafında zaten iyi çalışıyor ama json tipinin native binary format’ı bir adım daha öne geçiyor.

Production’da bu migration için sıfır downtime stratejisi: yeni tabloyu paralel kur, trigger ile çift yazım yap (eski + yeni), arka planda backfill batch’leri çalıştır, uygulamayı yeni tabloya yönlendir, eski tabloyu ROLLBACK için bir hafta tut, sonra drop. Aşağıdaki sorgu backfill batch’i için pratik:

-- Backfill batch — ON CONFLICT olmadığı için MERGE ile
DECLARE @batch INT = 50000;
WHILE 1 = 1
BEGIN
MERGE dbo.WebSiteLogs AS tgt
USING (
SELECT TOP (@batch) o.log_id, CAST(o.log_data AS JSON) AS log_data
FROM dbo.WebSiteLogs_Old o
WHERE NOT EXISTS (
SELECT 1 FROM dbo.WebSiteLogs n WHERE n.log_id = o.log_id)
ORDER BY o.log_id
) AS src
ON tgt.log_id = src.log_id
WHEN NOT MATCHED THEN
INSERT (log_id, log_data) VALUES (src.log_id, src.log_data);
IF @@ROWCOUNT < @batch BREAK;
WAITFOR DELAY '00:00:01'; -- diğer trafiğe nefes
END

9. Workshop 2 – .modify() vs JSON_MODIFY Performansı

İki farklı yaklaşımı aynı dataset üzerinde ölçeceğiz. Senaryo: 200 bin log satırının her birinde meta.agent property’sini güncelleyeceğiz.

-- Eski yöntem: nvarchar(max) tablosu üzerinde JSON_MODIFY
SET STATISTICS TIME, IO ON;
UPDATE dbo.WebSiteLogs_Old
SET log_data = JSON_MODIFY(log_data, '$.meta.agent', 'Chrome/120');
SET STATISTICS TIME, IO OFF;
-- Yeni yöntem: json tablosu üzerinde .modify()
SET STATISTICS TIME, IO ON;
UPDATE dbo.WebSiteLogs
SET log_data.modify('$.meta.agent', 'Chrome/120');
SET STATISTICS TIME, IO OFF;


Aynı 200 bin satırlık update, eski yaklaşım vs .modify() – CPU ve elapsed time karşılaştırması.

Test sonuçlarım: nvarchar(max) tablosunda 12.4 saniye CPU, 18 saniye elapsed time. json tablosunda 4.1 saniye CPU, 6.8 saniye elapsed. Yaklaşık 2-3 kat hız farkı, I/O profilinde de logical read sayıları daha düşük (yeniden yazılan sayfa sayısı az olduğu için).

Bu farkın iki ana sebebi var: (1) JSON_MODIFY her satırda tüm dokümanı parse ediyor, .modify() ise sadece ilgili offset’e gidiyor. (2) Yeni değer eski değerden büyük değilse .modify() in-place yazım yapıyor; engine yeni page yazmadan değişikliği uygulayabiliyor.

Önemli not: .modify() preview olduğu için PREVIEW_FEATURES açık olmayan database’lerde çalışmaz. Üretim ortamına geçerken bu detayı release notes’a ekleyin, DBA arkadaşınız varsa kafa kafaya verin.

10. Workshop 3 – JSON_OBJECTAGG ile API Response İnşası

Klasik bir REST API response senaryosu kuralım: bir sipariş için müşteri bilgisi, satır kalemleri ve toplam tutarı tek bir JSON object olarak istemcinin önüne koyacağız. Eski yaklaşımda FOR JSON PATH ile yapardık; ama dinamik key’ler veya GROUP BY ile birleştirme gerektiren durumlarda zorlanırdık.

-- Test verisi
DROP TABLE IF EXISTS #orders, #order_items;
CREATE TABLE #orders
(
order_id INT PRIMARY KEY,
customer NVARCHAR(100),
order_date DATE
);
CREATE TABLE #order_items
(
order_id INT,
sku NVARCHAR(20),
qty INT,
price DECIMAL(10,2)
);
INSERT INTO #orders VALUES
(1001, 'Yavuz', '2026-05-05'),
(1002, 'Mehmet', '2026-05-05');
INSERT INTO #order_items VALUES
(1001, 'SKU-A', 2, 50.00),
(1001, 'SKU-B', 1, 120.00),
(1002, 'SKU-C', 3, 30.00);
-- API response — JSON_OBJECT + JSON_ARRAYAGG kombinasyonu
SELECT
JSON_OBJECT(
'order_id' : o.order_id,
'customer' : o.customer,
'order_date' : CONVERT(NVARCHAR(10), o.order_date, 23),
'items' : (
SELECT JSON_ARRAYAGG(
JSON_OBJECT(
'sku' : i.sku,
'qty' : i.qty,
'price' : i.price,
'total' : i.qty * i.price
ABSENT ON NULL RETURNING JSON)
)
FROM #order_items i
WHERE i.order_id = o.order_id
),
'grand_total': (
SELECT SUM(i.qty * i.price)
FROM #order_items i
WHERE i.order_id = o.order_id
)
ABSENT ON NULL RETURNING JSON) AS api_response
FROM #orders o
ORDER BY o.order_id;

Çıktı, API gateway’inin doğrudan istemciye basabileceği temizlikte. ABSENT ON NULL sayesinde NULL değerler otomatik atılıyor, RETURNING JSON ile json tipinde dönüyor. Eski FOR JSON PATH yaklaşımıyla kıyasla iki kazanım: nested aggregation daha temiz (GROUP BY senaryolarında özellikle), ve nvarchar dönüş yerine native json, sonradan başka bir json kolona INSERT etmek istersen ek CAST gerekmiyor.

Reporting view’larında bu pattern hızla yayılıyor. Eski STRING_AGG + manuel quote escape eden raporları gözden geçirip JSON_OBJECTAGG’e çevirmek hem daha okunaklı hem daha güvenli (escape engine tarafında, manuel hata yapma şansı sıfır).

11. Workshop 4 – JSON Index ile Filter Performansı

Şimdi en heyecanlı kısma geliyoruz: yeni JSON Index gerçekten işe yarıyor mu? Workshop 1’deki WebSiteLogs tablosu üzerinde ölçeceğiz.

-- Önce index'siz bir filter çalıştır
SET STATISTICS IO, TIME ON;
SELECT COUNT(*)
FROM dbo.WebSiteLogs
WHERE JSON_VALUE(log_data, '$.event') = 'login'
AND JSON_VALUE(log_data, '$.meta.ip') = '10.0.0.1';
-- Aşağıdakine benzer planı görürsün:
-- Clustered Index Scan + filter (full scan)
-- JSON Index oluştur
CREATE JSON INDEX wlogs_idx
ON dbo.WebSiteLogs (log_data);
-- Aynı sorguyu tekrar çalıştır
SET STATISTICS IO, TIME ON;
SELECT COUNT(*)
FROM dbo.WebSiteLogs
WHERE JSON_VALUE(log_data, '$.event') = 'login'
AND JSON_VALUE(log_data, '$.meta.ip') = '10.0.0.1';

JSON Index öncesi/sonrası query plan: Clustered Index Scan’den JSON Index Seek’e geçiş, logical read sayısında ciddi düşüş.

Test sonucum: index’siz tarafta 200 bin satırlık tabloda 1.2 saniye, ~9000 logical read. Index’li tarafta 80 ms ve 240 logical read. 15 kat hız farkı, en kritik bilgi ise CPU kullanımının düşmesi — JSON Index parse cycle’ı tamamen ortadan kaldırıyor.

JSON_PATH_EXISTS ve JSON_CONTAINS predicate’leri de aynı index’ten faydalanıyor:

-- 'meta.agent' property var mı?
SELECT COUNT(*)
FROM dbo.WebSiteLogs
WHERE JSON_PATH_EXISTS(log_data, '$.meta.agent') = 1;
-- meta objesi belirli bir alt JSON içeriyor mu?
SELECT COUNT(*)
FROM dbo.WebSiteLogs
WHERE JSON_CONTAINS(log_data, '"Mozilla"', '$.meta.agent') = 1;

Hatırlatma: index, LIKE ve IS NULL predicate’lerinde işe yaramıyor şu sürümde. Eğer log_data içinde substring araması yapıyorsanız (LIKE ‘%error%’ gibi), JSON Index size yardım etmez; bu durumda full text index veya computed column + standart index hilesini düşünmeniz gerekir.

12. Kısıtlar ve Prod Kontrol Listesi

  • .modify() metodu ve CREATE JSON INDEX SQL Server 2025’te preview — PREVIEW_FEATURES açık olmalı.
  • Azure SQL Database, Azure SQL MI (AUTD veya 2025 update policy), Fabric Data Warehouse’da JSON_OBJECTAGG / JSON_ARRAYAGG / JSON Index zaten GA.
  • JSON Index için tablo clustered primary key’e sahip olmalı.
  • JSON Index online build desteklemiyor — büyük tablolarda bakım penceresinde kur.
  • JSON kolonlu satırlarda Optimized Locking SIL devreye giremez — geçen haftaki yazıyı hatırla.
  • Tek doküman 2 GB sınırı, 32K unique key, 65535 array eleman — büyük log dokümanlarında kontrol et.
  • OPENJSON şu sürümde bazı platformlarda hâlâ implicit cast bekliyor; explicit CAST(json_col AS NVARCHAR(MAX)) güvenli yol.
  • sp_describe_first_result_set bazı durumlarda json yerine varchar(max) raporluyor — ORM layer’larında bunu test et.

Production’a almadan önce şu kontrol listesi pratik:

(1) Mevcut nvarchar(max) kolon kullanımlarını çıkar, geçişin storage etkisini sp_spaceused ile öncesi/sonrası ölç.

(2) JSON_MODIFY çağrılarınızı .modify() ile değiştirmeden önce performans testi yap.

(3) JSON Index ekleyeceğin sorguların execution plan’ına bak — gerçekten seek’e düşüyor mu, scan ediyor mu?

(4) ORM tarafında EF Core 10+ json kolonu için type mapping destekliyor; alt sürümlerde manuel CAST gerekebilir.

13. Optimized Locking ile Etkileşim – Lock ile ilişkisi

Geçen haftaki Optimized Locking yazısında SIL’in (Skip Index Locks) JSON kolonlu satırlarda devre dışı kaldığını söylemiştim. Bu detay kritik: yeni json tipini kullanmaya başladığınızda ilgili tablonun update’lerinde OL’nin SIL faydası kayboluyor. TID locking ve LAQ kazanımları devam ediyor ama row/page lock atlama avantajı yok.

Production’da bu, JSON kolonlarını içeren tabloların lock memory profilinin diğer tablolardan biraz daha yüksek olacağı anlamına geliyor. Workshop’larda ölçtüğümüz .modify() hız farkı SIL kaybını telafi etmenin çok ötesinde, ama ölçek büyüdükçe (milyonlarca satır + yüksek concurrency) sys.dm_os_memory_clerks üzerinden lock memory’i izlemek mantıklı. Bu, üzerinde durulması gereken bir trade-off.

14. Bitiriyoruz

Native JSON Data Type, SQL Server 2025’in en konuşulan ama belki en az anlaşılan yenilik. Konuşulan, çünkü JSON’la çalışan herkesi etkiliyor; az anlaşılan, çünkü preview feature’lar (.modify, JSON Index, JSON_CONTAINS) ayrı bir flag’in arkasında. Yazıdaki kod örneklerini kendi laboratuvarınızda çalıştırırken PREVIEW_FEATURES’ı açmayı unutmayın.

Workshop’ları sırayla yapmanızı tavsiye ederim. Migration → .modify benchmarking → JSON_OBJECTAGG ile rapor üretimi → JSON Index ölçümü zinciri, JSON saklayan bir uygulama için adım adım modernizasyon roadmap’ı niteliğinde. Storage farkını sp_spaceused ile öncesi/sonrası göstermek leadership tarafına da güzel bir hikaye olur.

Native JSON ile ilgili sorularınızı yorumlara yazabilirsiniz. Özellikle migration sırasında karşılaştığınız, beklemediğiniz storage veya lock davranışı varsa paylaşırsanız ikinci bölümde örnek olarak işlerim. .modify() metodunu büyük ölçekte deneyenlerin sonuçları benim için de kıymetli, preview döneminden GA’ya geçişte Microsoft’un odaklanması gereken senaryoları bu şekilde ortaya çıkarıyoruz.


Yazar hakkında

Yavuz Filizlibay — Database Solution Architect

SQL Server ekosisteminde uzun yıllardır performans, güvenlik, yüksek erişilebilirlik üzerine çalışıyorum. SQL Server Administration, Querying, Performans ve Security eğitimleri ile danışmanlık hizmeti veriyorum. Yeni makalelerden haberdar olmak için LinkedIn’de bağlanabilir, eğitim veya danışmanlık için iletişime geçebilirsiniz.


Yavuz Filizlibay sitesinden daha fazla şey keşfedin

Subscribe to get the latest posts sent to your email.

,

Bir Cevap Yazın

Bu site istenmeyenleri azaltmak için Akismet kullanır. Yorum verilerinizin nasıl işlendiğini öğrenin.

Yavuz Filizlibay sitesinden daha fazla şey keşfedin

Okumaya devam etmek ve tüm arşive erişim kazanmak için hemen abone olun.

Okumaya Devam Edin

Yavuz Filizlibay sitesinden daha fazla şey keşfedin

Okumaya devam etmek ve tüm arşive erişim kazanmak için hemen abone olun.

Okumaya Devam Edin