
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 tabloCREATE 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 gerekmiyorINSERT 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ımDROP 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ımSELECT 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 arraySELECT TOP (5) c.object_id, JSON_ARRAYAGG(c.name ORDER BY c.column_id) AS column_listFROM sys.columns AS cGROUP 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 toplaDECLARE @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 settingsFROM @tGROUP 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ımALTER TABLE Sales.SalesOrderHeader ADD Info JSON NULL;-- JSON Index oluşturCREATE JSON INDEX sales_info_idx ON Sales.SalesOrderHeader (Info);-- Bu sorgu artık index'ten faydalanırSELECT COUNT(*)FROM Sales.SalesOrderHeaderWHERE 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 indexCREATE JSON INDEX sales_info_idx_arr ON Sales.SalesOrderHeader (Info) WITH (OPTIMIZE_FOR_ARRAY_SEARCH = ON);-- Index'lerin durumunu görüntüleSELECT i.name, OBJECT_NAME(i.object_id) AS table_name, ji.optimize_for_array_searchFROM sys.json_indexes jiJOIN 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 otomatikINSERT 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 ileDECLARE @batch INT = 50000;WHILE 1 = 1BEGIN 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 nefesEND
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_MODIFYSET 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 verisiDROP 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 kombinasyonuSELECT 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_responseFROM #orders oORDER 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ırSET STATISTICS IO, TIME ON;SELECT COUNT(*)FROM dbo.WebSiteLogsWHERE 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şturCREATE JSON INDEX wlogs_idx ON dbo.WebSiteLogs (log_data);-- Aynı sorguyu tekrar çalıştırSET STATISTICS IO, TIME ON;SELECT COUNT(*)FROM dbo.WebSiteLogsWHERE 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.WebSiteLogsWHERE JSON_PATH_EXISTS(log_data, '$.meta.agent') = 1;-- meta objesi belirli bir alt JSON içeriyor mu?SELECT COUNT(*)FROM dbo.WebSiteLogsWHERE 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.
