BSON, MongoDB'nin belgeleri saklamak ve ağ üzerinden taşımak için kullandığı ikili serileştirme biçimidir. JSON'a benzer bir yapısı vardır ama JSON'da olmayan tipleri de içerir: ObjectId, Date, 32 ve 64 bit tam sayılar (Int32, Long), Double, yüksek hassasiyetli Decimal128, ikili veri (BinData), Timestamp ve düzenli ifade gibi. Her tipin bir numarası ve bir takma adı vardır; sorgularda $type operatörüyle bu numara veya adla filtreleme yapabilirsiniz.

Bu yazıda en sık kullanılan tiplerin nasıl davrandığını, mongosh'ta nasıl oluşturulduğunu, farklı tiplerin sıralamada nasıl karşılaştırıldığını ve BSON verisini JSON'a dönüştürürken tip bilgisini koruyan Extended JSON'u anlatıyoruz. Belge yapısının genel kuralları için MongoDB nedir, konuların tamamı için MongoDB öğrenme rehberi yazılarına bakabilirsiniz.

BSON tip tablosu

BSON tip tablosu: Double, String, Object, Array

Her BSON tipinin hem tam sayı hem metin tanımlayıcısı vardır. $type operatörü bunların ikisini de kabul eder; ayrıca int, decimal, double ve long tiplerinin hepsiyle eşleşen "number" takma adını destekler.

TipNumaraTakma adNot
Double1double
String2stringUTF-8
Object3objectGömülü belge
Array4array
Binary data5binDataAlt tipi vardır
Undefined6undefinedDeprecated
ObjectId7objectId12 bayt
Boolean8bool
Date9dateEpoch'tan beri milisaniye
Null10null
Regular Expression11regex
DBPointer12dbPointerDeprecated
JavaScript13javascript
Symbol14symbolDeprecated
JavaScript with scope15javascriptWithScopeDeprecated
32-bit integer16int
Timestamp17timestampDahili kullanım
64-bit integer18long
Decimal12819decimal34 basamak hassasiyet
Min key-1minKeyHer şeyden küçük
Max key127maxKeyHer şeyden büyük

Aggregation tarafında $type ifade operatörü argümanının BSON tipini döndürür, $isNumber ise argüman int, decimal, double veya long ise true döndürür. Sorgu operatörlerinin genel kullanımı sorgu operatörleri yazısında anlatılıyor.

ObjectId: varsayılan _id değeri

ObjectId küçük, büyük olasılıkla benzersiz, hızlı üretilen ve sıralı bir değerdir. 12 bayttan oluşur:

  • 4 bayt zaman damgası: Oluşturulma anı, Unix epoch'tan beri saniye olarak.
  • 5 bayt rastgele değer: İstemci tarafındaki her süreç için bir kez üretilir; makineye ve sürece özgüdür.
  • 3 bayt sayaç: Süreç başına artan, rastgele bir değerle başlatılan sayaç.

Zaman damgası ve sayaç baytları büyük uçlu (big-endian) yazılır; diğer BSON değerleri ise küçük uçludur. Bir belgeyi _id vermeden eklediğinizde sürücü sizin için bir ObjectId üretir. Oluşturulma zamanını ve metin karşılığını mongosh'ta şöyle okursunuz:

javascript
ObjectId("507c7f79bcf86cd7994f6c0e").getTimestamp()
// çıktı: ISODate("2012-10-15T21:26:17Z")

ObjectId("507f191e810c19729de860ea").toString()
// çıktı: 507f191e810c19729de860ea

ObjectId() argümansız çağrılırsa yeni bir değer üretir; 24 karakterlik onaltılık bir metin verirseniz o değeri kullanır; bir tam sayı verirseniz bu sayı (saniye cinsinden) zaman damgasının yerine geçer. Bu son özellik, belirli bir tarihten önce oluşturulmuş belgeleri _id üzerinden aramak için kullanılabilir:

javascript
const sinir = ObjectId( Math.floor( new Date( "2026-01-01" ) / 1000 ) )
db.siparisler.find( { _id: { $lt: sinir } } )

_id için tek seçenek ObjectId değildir. Belgeler doğal ve benzersiz bir tanımlayıcı varsa onu kullanmayı (yer kazandırır, ek indeks gerektirmez), otomatik artan bir sayı üretmeyi veya uygulamada UUID üretmeyi de sayar. UUID kullanacaksanız koleksiyonda ve _id indeksinde daha verimli saklamak için onu BSON BinData tipinde tutmanız önerilir. Mevcut kayıtla çakışan bir _id eklemek E11000 duplicate key hatasına yol açar.

Date ve Timestamp: hangisini kullanmalısınız?

BSON Date, Unix epoch'tan (1 Ocak 1970) beri geçen milisaniyeyi tutan 64 bit, işaretli bir tam sayıdır; negatif değerler 1970 öncesini gösterir ve geçmişe ve geleceğe doğru yaklaşık 290 milyon yıllık bir aralık kapsar. Resmî BSON belirtimi bu tipe "UTC datetime" der. mongosh'ta Date oluşturmanın iki yolu vardır: new Date() ve ISODate(). İkisi de ISODate() sarmalayıcısıyla gösterilen bir Date nesnesi döndürür. new olmadan çağrılan Date() ise geçerli tarihi metin olarak döndürür:

javascript
var metinTarih = Date()
typeof metinTarih
// çıktı: string

var tarih = ISODate("2021-03-21T06:00:00.171Z")
Object.prototype.toString.call(tarih) === "[object Date]"
// çıktı: true

Bu ayrım önemli bir hatanın kaynağıdır: Date() sonucunu bir alana yazarsanız tarih değil metin saklarsınız ve tarih aralığı sorguları beklediğiniz gibi çalışmaz. Tarih alanlarını her zaman new Date() veya ISODate() ile oluşturun.

BSON Timestamp ise MongoDB'nin dahili kullanımı içindir ve Date ile ilgisi yoktur. 64 bitin yüksek 32 biti saniye cinsinden zaman (time_t), düşük 32 biti aynı saniyedeki işlemler için artan bir sıra numarasıdır. Replikasyondaki oplog'un ts alanı bu tipi kullanır. Belgeler uygulama geliştirmede neredeyse her durumda Date tipini önerir. mongosh'ta Timestamp() ve Timestamp(0, 0) değerleri normal koleksiyonlarda sunucu tarafında zaman damgası üretir, time series koleksiyonlarında üretmez.

Sayı tipleri: Int32, Long, Double ve Decimal128

Sayı tipleri: Int32, Long, Double ve Decimal128: Int32, Long (Int64), Double, Decimal128

mongosh'ta bir alana sade bir sayı yazdığınızda, sayı 32 bit tam sayıya dönüştürülebiliyorsa Int32, dönüştürülemiyorsa Double olarak saklanır. Tipi kendiniz belirlemek için yapıcıları kullanırsınız:

javascript
db.olcumler.insertMany( [
  { _id: 1, deger: 1 },
  { _id: 2, deger: Long("9007199254740993") },
  { _id: 3, deger: Double(1) },
  { _id: 4, deger: Decimal128("19.90") },
  { _id: 5, deger: 3200000001 }
] )
db.olcumler.find( { deger: { $type: "int" } } )
db.olcumler.find( { deger: { $type: "number" } } )

Bu örnekte 1 numaralı belge Int32, 3 numaralı belge açıkça Double olarak saklanır. 5 numaralı belgedeki 3200000001 32 bit tam sayı sınırını aştığı için Double olur. İlk sorgu yalnızca Int32 değerli belgeyi, ikinci sorgu ise "number" takma adı dört sayı tipini de kapsadığı için beş belgenin hepsini döndürür. mongosh'ta NumberLong() yalnızca metin argüman kabul eder; Long() 64 bit değerlerle dönüşüm yöntemleri sunar.

Tipmongosh yapıcısıNe zaman kullanılır
Int32Int32(1) veya 32 bite sığan sade sayıSayaçlar, adetler
Long (Int64)Long("...")32 biti aşan tam sayılar
DoubleDouble(1) veya ondalıklı sade sayıÖlçüm gibi yaklaşık değerler
Decimal128Decimal128("19.90")Para ve tam ondalık hassasiyet gereken hesaplar

Para tutarları neden Double ile saklanmamalı?

İkili kayan noktalı sayılar onluk değerleri tam olarak temsil edemez. Belgelerdeki Node.js örneğinde 0.1 * 0.2 işlemi beklenen 0.02 yerine şu sonucu verir:

javascript
0.1 * 0.2
// çıktı: 0.020000000000000004

Finans ve bankacılık gibi hassasiyetin önemli olduğu alanlarda belgeler Decimal128 kullanmayı önerir. Decimal128, IEEE 754-2008 decimal128 biçimini kullanan 128 bitlik onluk bir gösterimdir; 34 anlamlı basamak ve -6143 ile +6144 arası üs aralığı destekler. Değeri metin olarak vermek (Decimal128("9823.1297")) onu olduğu gibi saklar; ondalık bir double değerden üretmek için Decimal128.fromStringWithRounding() vardır. Decimal128 değerleri diğer sayı tipleriyle gerçek sayısal değerlerine göre karşılaştırılır ve sıralanır.

String ve ikili veri

BSON metinleri UTF-8'dir; sürücüler dilin kendi metin biçimini serileştirirken UTF-8'e dönüştürür. Bu sayede Türkçe karakterler sorunsuz saklanır ve $regex sorguları UTF-8 destekler. Ancak sort() varsayılan olarak C++ strcmp ile basit ikili karşılaştırma yaptığı için bazı karakterleri yanlış sıralayabilir. Dile duyarlı sıralama için collation kullanılır; büyük-küçük harf duyarsız aramalar regex sorguları yazısında anlatılıyor.

BinData bir bayt dizisidir ve verinin nasıl yorumlanacağını gösteren bir alt tipi (subtype) vardır. BinData değerleri önce uzunluğa, sonra alt tipe, en son bayt bayt veriye göre sıralanır.

Farklı tipler nasıl karşılaştırılır ve sıralanır?

Bir alanda farklı tiplerde değerler bulunabildiği için MongoDB, sıralamada tipleri sabit bir sırayla karşılaştırır. En küçükten en büyüğe:

  • MinKey (dahili tip)
  • Null
  • Sayılar (int, long, double, decimal)
  • Symbol, String
  • Object
  • Array
  • BinData
  • ObjectId
  • Boolean
  • Date
  • Timestamp
  • Regular Expression
  • JavaScript Code
  • JavaScript Code with Scope
  • MaxKey (dahili tip)
  • Sayılar eşdeğerdir: Karşılaştırmada tüm sayı tipleri aynı kabul edilir; Int32 olan 5 ile Double olan 5 eşittir.
  • Olmayan alan null sayılır: { } ve { a: null } belgeleri a alanına göre sıralamada eşdeğerdir.
  • Date, Timestamp'tan önce gelir.
  • Dizilerde: Artan sıralama dizinin en küçük öğesine, azalan sıralama en büyük öğesine göre yapılır. Boş dizi, null değerden veya eksik alandan önce gelir.
  • Sorgu operatörlerinde tip ayrımı: $gt ve $lt gibi karşılaştırma operatörleri yalnızca alanın BSON tipi sorgu değerinin tipiyle eşleşen belgelerde uygulanır (type bracketing). Örneğin sayıyla yapılan bir $gt sorgusu metin değerli belgeleri getirmez.

Sıralama ve sayfalama ayrıntıları sort, limit ve skip yazısında yer alıyor.

Extended JSON: BSON'u JSON'a tip kaybı olmadan çevirmek

Extended JSON: BSON'u JSON'a tip kaybı olmadan çevirmek: ObjectId, Int32, Int64, Double

JSON, BSON tiplerinin yalnızca bir kısmını doğrudan gösterebilir. Tip bilgisini korumak için MongoDB, $ önekli anahtarlar kullanan Extended JSON biçimini tanımlar. İki modu vardır: Canonical mod tip bilgisini korumayı okunabilirliğe tercih eder; Relaxed mod okunabilirliği ve birlikte çalışabilirliği öne çıkarır ama BSON'a geri dönüşte tip bilgisi kaybolabilir. İki biçim de geçerli JSON'dur.

TipCanonicalRelaxed
ObjectId{"$oid":"507f191e810c19729de860ea"}Canonical ile aynı
Int32{"$numberInt":"42"}42
Int64{"$numberLong":"42"}42
Double{"$numberDouble":"10.5"}10.5
Decimal128{"$numberDecimal":"19.90"}Canonical ile aynı
Date (1970 ile 9999 arası){"$date":{"$numberLong":"<milisaniye>"}}{"$date":"<ISO-8601 tarih>"}
Timestamp{"$timestamp":{"t":<saniye>,"i":<artış>}}Canonical ile aynı
Regex{"$regularExpression":{"pattern":"^K","options":"i"}}Canonical ile aynı

Tablodan görüldüğü gibi Relaxed modda Int32, Int64 ve Double değerleri sade sayıya dönüşür; dosyayı tekrar okuyan araç hangi tipin kastedildiğini bilemez. Tip kaybının sorun olacağı yedekleme ve aktarma işlerinde canonical mod daha güvenlidir. Aşağıdaki geçerli bir canonical Extended JSON belgesidir:

json
{
  "_id": { "$oid": "507f191e810c19729de860ea" },
  "adet": { "$numberInt": "3" },
  "tutar": { "$numberDecimal": "19.90" },
  "olusturma": { "$date": { "$numberLong": "1767225600000" } }
}

mongosh'ta EJSON.serialize() bir BSON nesnesini Extended JSON'a, EJSON.deserialize() geri BSON tiplerine çevirir; EJSON.stringify() ve EJSON.parse() ise metinle çalışır. Komut satırından mongosh --json=canonical veya --json=relaxed ile sonuçları doğrudan Extended JSON olarak alabilirsiniz (mongosh kullanımı). Veri aktarma araçlarının Extended JSON kullanımı mongoexport ve mongoimport yazısında. JavaScript'in yerleşik JSON.stringify davranışını merak ediyorsanız JSON.parse ve JSON.stringify yazımıza bakabilirsiniz.

Tip kontrolü nasıl yapılır?

mongosh'ta JavaScript'in typeof operatörü number veya object gibi genel sonuçlar döndürür, Int32 veya ObjectId gibi özel tipleri ayırt etmez. instanceof da güvenilir değildir; sunucudan gelen BSON değerlerini kullanıcının oluşturduğu değerlerden farklı bir temel sınıfa bağlayabilir. Belgeler bunun yerine $type sorgu operatörünü veya nesnenin yapıcısını incelemeyi önerir. Bir koleksiyonda yanlış tipte kaydedilmiş alanları bulmak için örneğin db.siparisler.find( { tarih: { $type: "string" } } ) sorgusu, tarih alanı metin olarak saklanmış siparişleri listeler.

MongoDB 9.0 ile BSON doğrulama

Sürüm notlarına göre MongoDB 9.0, sunucunun aldığı tüm BSON nesnelerini varsayılan olarak doğrular; geçersiz BSON verisi geldiğinde hata döndürür. Bu nedenle net.wireObjectCheck yapılandırma seçeneği ve karşılık gelen --objcheck komut satırı argümanı MongoDB 9.0 ile deprecated oldu. Diğer değişiklikler MongoDB 9.0 yenilikler yazısında.

Sık yapılan hatalar

  • Tarihi metin olarak saklamak: Date() metin döndürür; new Date() veya ISODate() kullanın.
  • Parayı Double ile tutmak: Yuvarlama hatalarından kaçınmak için Decimal128 kullanın ve değeri metin olarak verin.
  • Timestamp'ı tarih için kullanmak: Timestamp dahili kullanım içindir.
  • Aynı alanda karışık tipler: Karşılaştırma operatörleri tip ayrımı yaptığı için bazı belgeler sorgu sonucundan sessizce düşer. Tutarlılık için schema validation kullanabilirsiniz.
  • ObjectId sırasına kesin zaman sırası gibi güvenmek: Sıra yaklaşık bir sıradır.
  • Extended JSON'un relaxed modunda tip beklemek: Relaxed çıktıyı geri okurken sayı tipleri kaybolabilir.

Sık Sorulan Sorular

BSON ile JSON arasındaki temel fark nedir?

JSON metin tabanlıdır ve az sayıda tip içerir. BSON ikili bir biçimdir, alan sırasını korur ve tarih, 64 bit tam sayı, Decimal128, ObjectId ve ikili veri gibi ek tipler sunar.

Deprecated tipleri kullanmalı mıyım?

Hayır. Undefined, DBPointer, Symbol ve JavaScript with scope tipleri deprecated olarak işaretlidir. Eski verinizde undefined değerler varsa belgelerde bunların taşınmasına ayrılmış bir sayfa bulunur.

ObjectId'den belgenin oluşturulma tarihini alabilir miyim?

Evet, getTimestamp() yöntemiyle. Ancak çözünürlük bir saniyedir ve değer istemcinin saatine bağlıdır.

_id alanına hangi tipleri yazabilirim?

Dizi, regex ve undefined dışındaki tüm BSON tiplerini. Replikasyonun düzgün çalışması için belgeler _id alanında regex tipinde değer saklamamanızı özellikle uyarır.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: MongoDB öğrenme rehberi · MongoDB nedir · insertOne ve insertMany · Sorgu operatörleri · Schema validation