DynamoDB JOIN: Tabloları Nasıl Birleştirirsiniz (ve Neden Genellikle Yapamazsınız)
DynamoDB'de JOIN yoktur. API'nin bir birleştirme operatörü yok, veri modelinin
yabancı anahtarları yok ve — çoğu insanı şaşırtan kısım — SQL tadındaki sorgu
katmanı olan PartiQL de bir tane eklemez. Bir PartiQL SELECT tam olarak bir
tablo okur.
İlişkisel bir veritabanından geldiyseniz, bu çarptığınız ilk duvardır. Bu kılavuz, duvarın neden orada olduğunu, geliştiricilerin bunun yerine yaptığı dört şeyi, gerçekten bir gerçek birleştirmeye ihtiyaç duyduğunuz tek durumu — ve bir tanesini nasıl çalıştıracağınızı kapsar.
DynamoDB JOIN yapabilir mi?
Hayır. DynamoDB tabloları birleştiremez — ne düşük seviyeli API aracılığıyla
(GetItem / Query / Scan / BatchGetItem), ne PartiQL aracılığıyla, ne de
herhangi bir yerleşik sorgu planlayıcı aracılığıyla, çünkü bir sorgu planlayıcı
yoktur. Her okuma tek bir tabloya veya onun indekslerinden birine eşlenir. İki
tabloyu eşleşen bir anahtar üzerinde birleştirmek, DynamoDB öğeleri geri verdikten
sonra yaptığınız bir şeydir, asla içinde değil.
- DynamoDB'de
JOINoperatörü yoktur. Hiç olmadı. - PartiQL'in
SELECT'i yalnızca tek tabloludur — gramer kelimenin tam anlamıylaSELECT … FROM {{table}}[.{{index}}]şeklindedir ve onu iki tabloya yöneltmekValidationException: Only Select from a Single Table or index supporteddöndürür. - AWS'nin önerdiği çözüm, bir birleştirmeye ihtiyaç duymamaktır: normalden çıkarın ya da ilgili öğelerin tek bir istekte getirdiğiniz bir bölümde yaşaması için tek tablo tasarımı kullanın.
- Gerçek tablolar arası / geçici durum için, birleştirmeyi DynamoDB'nin dışında yaparsınız — uygulamanızda ya da sizin için yapan bir araçla.
DynamoDB birleştirme yapabilir mi?
Hayır. DynamoDB tabloları birleştiremez — ne düşük seviyeli API aracılığıyla
(GetItem / Query / Scan / BatchGetItem), ne PartiQL aracılığıyla, ne de
herhangi bir yerleşik sorgu planlayıcı aracılığıyla, çünkü bir sorgu planlayıcı
yoktur. Her okuma tek bir tabloya veya onun indekslerinden birine eşlenir. İki
tabloyu eşleşen bir anahtar üzerinde birleştirmek, DynamoDB öğeleri geri verdikten
sonra yaptığınız bir şeydir, asla içinde değil.
Bu, AWS'nin doldurmayı unuttuğu bir boşluk değildir. Bilinçli bir tasarım kararıdır ve bir geçici çözüme uzanmadan önce gerekçeyi anlamak değerlidir.
DynamoDB'nin neden birleştirmesi yoktur
Bir SQL JOIN, veritabanından birden çok tabloyu okumasını ve sorgu anında bir
araya getirmesini ister. AWS'nin kendi
ilişkisel verileri modelleme kılavuzu
maliyeti açıkça belirtir: şöyle bir sorgu
SELECT * FROM Orders
INNER JOIN Order_Items ON Orders.Order_ID = Order_Items.Order_ID
INNER JOIN Products ON Products.Product_ID = Order_Items.Product_ID
ORDER BY Quantity_on_Hand DESCesnektir ama "sorgudaki her birleştirme sorgunun çalışma zamanı karmaşıklığını artırır çünkü her tablonun verisinin hazırlanması ve ardından bir araya getirilmesi gerekir." Bu iş sınırsızdır — maliyeti sorguya değil veriye bağlıdır — ki bu tam olarak DynamoDB'nin sahip olmayı reddettiği özelliktir.
Bu yüzden AWS kısıtlamayı tasarıma dahil etti. DynamoDB, onların deyimiyle, "her
ikisi de [CPU ve ağ] kısıtlamalarını JOIN'leri ortadan kaldırarak (ve
verinin normalden çıkarılmasını teşvik ederek) en aza indirmek ve veritabanı
mimarisini bir uygulama sorgusunu bir öğeye tek bir istekle tamamen yanıtlayacak
şekilde optimize etmek üzere kurulmuştur." Bunlar, herhangi bir ölçekte tek haneli
milisaniyelik gecikme satın alan niteliklerdir: bir DynamoDB okumasının çalışma
zamanı maliyeti, tablo boyutundan bağımsız olarak sabittir. Tasarım gereği, plan
yapacak bir birleştirme motoru ve yabancı anahtar kavramı yoktur.
"Ama PartiQL SQL'dir, kesin birleştirir?"
Hayır. PartiQL size DynamoDB üzerinde SELECT / INSERT / UPDATE / DELETE
söz dizimi verir, ancak SQL ile uyumludur, SQL değildir.
Resmi SELECT grameri
şöyledir:
SELECT {{expression}} [, ...]
FROM {{table}}[.{{index}}]
[ WHERE {{condition}} ]
[ ORDER BY {{key}} [DESC|ASC], ... ]FROM bir tablo alır (isteğe bağlı olarak onun indekslerinden biri). İkinci
bir FROM tablosu, JOIN, alt sorgu veya CTE yoktur. PartiQL'i iki tabloya
yöneltin ve DynamoDB onu reddeder
(AWS re:Post'ta bildirildi):
ValidationException: Only Select from a Single Table or index supportedPartiQL'in neden SQL'e benzediği ama onun gibi davranamadığı hakkındaki tam gerekçeyi istiyorsanız, bkz. PartiQL vs SQL.
Geliştiricilerin gerçekten kullandığı 4 geçici çözüm
1. Normalden çıkarın (veriyi içeri kopyalayın)
Aksi takdirde birleştireceğiniz alanları doğrudan öğeye saklayın. Bir Order,
sonradan çözeceğiniz bir customerId yerine customerName ve shippingAddress
anlık görüntüsünü taşır. Tek okuma, birleştirme yok.
Bedeli yazma anındaki yayılmadır: kaynak değiştiğinde her kopyayı güncellersiniz (genellikle bir DynamoDB Streams işleyicisi aracılığıyla). Okuma karmaşıklığını yazma karmaşıklığıyla takas ediyorsunuz — okuma ağırlıklı bir uygulama için genellikle iyi bir takas.
2. Tek tablo tasarımı (bölümde önceden birleştirin)
İlgili varlıkları tek bir tabloda, paylaşılan bir bölüm anahtarı altına
koyun, böylece bir öğe koleksiyonu zaten birleştirilmiş sonuçtur. Bir müşteri ve
tüm siparişleri PK = "CUSTOMER#42"'yi paylaşır; tek bir Query müşteri öğesini
artı her sipariş öğesini döndürür — "birleştirme" yazma anında zaten gerçekleşti.
Query PK = "CUSTOMER#42"
→ CUSTOMER#42 / PROFILE (müşteri)
→ CUSTOMER#42 / ORDER#1001 (bir sipariş)
→ CUSTOMER#42 / ORDER#1002 (bir sipariş)
Bu, bire-çok ilişkilere DynamoDB'nin kanonik yanıtıdır. Tam yol gösterimi tek tablo tasarımı içinde.
3. Uygulama tarafında birleştirme (iki okuma, kodda dikme)
A tablosundan okuyun, geri aldığınız anahtarları alın, B tablosundan okuyun ve iki sonuç kümesini uygulamanızda birleştirin. Bu ilişkisel birleştirme mantığıdır — sadece veritabanı yerine kodunuzda çalışır:
// "Her siparişi müşteri adıyla al" — manuel birleştirme.
const {Items: orders} = await ddb.query({TableName: 'Orders' /* … */});
const customers = await Promise.all(
orders.map((o) => ddb.getItem({TableName: 'Customers', Key: {id: o.customerId}}))
);
const joined = orders.map((o, i) => ({
...o,
customerName: customers[i].Item?.name
}));Küçük yayılma için uygundur. Çok sayıda siparişle bir N+1 sorunu olur —
siparişleri listelemek için bir okuma, sonra sipariş başına bir okuma — ki bu
yavaştır ve okuma kapasitesini yakar. BatchGetItem (sonraki) o ikinci dalgayı tek
bir gidiş-dönüşte toplar.
4. BatchGetItem (tek gidiş-dönüş, birden çok tablo)
BatchGetItem
API'nin "iki tabloya aynı anda dokunma"ya en yakın geldiği yerdir: tek bir istek
"bir veya daha fazla öğenin özniteliklerini bir veya daha fazla tablodan"
döndürür, çağrı başına 100 öğe veya 16 MB'a kadar, hangisine önce ulaşırsa.
Bir uygulama tarafı birleştirmenin gidiş-dönüşlerini keser — ama bir birleştirme
değildir. "İstenen öğeleri birincil anahtarla tanımlarsınız"; bir ON koşulu
ve ilişkisel eşleştirme yoktur. Yine de anahtarları önceden bilmeniz ve yanıtları
kendiniz dikmeniz gerekir.
Gerçek bir JOIN'in kaçınılmaz olduğu durum
Dört geçici çözüm, üretim okuma yollarını iyi kapsar. Yetersiz kaldıkları yer, geçici, keşif amaçlı, analitik sorgudur — modellemediğiniz olan:
- Bir
Orderstablosu ve birCustomerstablosu genelinde "AB'deki hangi müşteriler geçen ay 500 doların üzerinde bir sipariş verdi?" - İki varlık türünü birleştiren tek seferlik bir veri kalitesi kontrolü.
- Raporlama ve toplamalar (
GROUP BY,SUM,COUNT) — ki DynamoDB'nin hiç operatörü yoktur.
Bunlar tam olarak bir bölüme önceden pişiremeyeceğiniz sorgulardır, çünkü tanımı
gereği onları soracağınızı bilmiyordunuz. İlişkisel içgüdü — bir JOIN yazmak —
burada doğru olandır. DynamoDB onu yerel olarak sunamaz ve PartiQL de sunamaz.
Olağan ağır çözüm, S3'e dışa aktarmak ve Athena ile sorgulamak ya da bir veri ambarına aktarmaktır. Bu, ölçekte gerçek analitik için doğrudur, ancak canlı tablonuza karşı şimdi yanıtlamak istediğiniz bir soru için çok fazla tesisat işidir.
DynoTable'ın SQL Workbench'i ile gerçek bir JOIN çalıştırma
DynoTable, SQL Workbench'i DynamoDB tablolarınız üzerinde gerçek
SQL — JOIN, GROUP BY ve toplama işlevleri dahil — çalıştıran bir masaüstü
DynamoDB istemcisidir. Öğeleri normal DynamoDB API'si aracılığıyla okur, sonra
sorgunun ilişkisel kısımlarını istemcide yürütür. Böylece şunu yazabilirsiniz:
SELECT c.name, SUM(o.total) AS spend
FROM Customers c
JOIN Orders o ON o.customerId = c.id
WHERE c.region = 'EU'
GROUP BY c.name
HAVING SUM(o.total) > 500— ve tanımlı bir ilişkisi olmayan tablolara ve JOIN anahtar sözcüğü olmayan bir
sorgu motoruna karşı bir sonuç kümesi alırsınız.
Dürüst uyarı — "DynamoDB'nin erişim deseni kuralları içinde": Workbench yine
de DynamoDB aracılığıyla okur, bu yüzden sınırsız bir birleştirme sınırsız bir
okumadır. En hızlı sorgular, WHERE yan tümcesinin (ya da birleştirmenin ON
özniteliğinin) en az bir tarafta bir bölüm anahtarına veya bir
GSI'ya isabet ettiği sorgulardır, böylece DynamoDB
birleştirme yürütülmeden önce tam bir tablo taraması
yerine bir Query çalıştırır. Workbench bu kılavuzdaki kısıtlamaları kaldırmaz —
sadece dikmeyi elle yazmak yerine SQL sorusunu sormanıza izin verir ve altta ne
yaptığını size söyler.
"Evet, birleştirebilirsiniz" diyen, aslında doğru olan tek şey budur: PartiQL ve
AWS'nin kendi
NoSQL Workbench'i
— işlem oluşturucusu tek tablolu veri düzlemi işlemleriyle sınırlıdır
(Query / Scan / GetItem) — her ikisi de tek tablo duvarında durur, diğer çoğu
GUI istemcisi gibi. DynoTable'ın bir
DynamoDB GUI olarak nasıl karşılaştırıldığına bakın.
SSS
PartiQL JOIN'i destekler mi?
Hayır. PartiQL'in SELECT'i tek bir tabloyu (ya da indekslerinden birini) okur.
Çok tablolu bir sorgu ValidationException: Only Select from a Single Table or index supported döndürür. API'nin geri kalanıyla aynı duvar.
Tek bir sorguda iki DynamoDB tablosunu birleştirebilir misiniz?
Yerel olarak hayır. DynamoDB API'sinin iki tabloyu okuyup bir anahtar üzerinde
eşleştiren bir ifadesi yoktur. BatchGetItem tek bir istekte birden çok tablodan
öğe okuyabilir, ama bir ON koşulu yoktur — birincil anahtarla adlandırdığınız
öğeleri döndürür ve eşleştirmeyi size bırakır. Gerçek bir JOIN … ON … yalnızca
DynamoDB'nin dışında gerçekleşir: uygulamanızda ya da DynoTable'ın SQL
Workbench'inde.
Bir tabloyu kendi GSI'sine birleştirebilir misiniz?
Hayır — bir Global İkincil İndeks birleştirdiğiniz ayrı bir
tablo değildir; aynı öğelerin alternatif bir anahtar görünümüdür. Belirli bir
SELECT'te ya tabloyu ya da indeksi Query edersiniz, ikisini birlikte
birleştirmezsiniz. Bir GSI, öğelere farklı bir anahtarla ulaşmanızı sağlar ki bu
çoğu zaman bir birleştirme ihtiyacını baştan ortadan kaldırır.
İki AWS hesabı genelinde (ya da farklı hesaplardaki iki tablo) birleştirebilir
misiniz?
Yerel olarak hayır ve BatchGetItem ile de hayır — tek bir istek kimlik
bilgilerini kapsayamaz ve hesaplar arası birleştirme ilkeli yoktur. Her tabloyu
kendi hesabının kimlik bilgileriyle okur ve sonuçları uygulamanızda ya da
DynoTable'ın Workbench'i gibi bir araçta birleştirirsiniz.
Normalden çıkarma gerçekten bir birleştirmeden daha mı iyi? DynamoDB'nin hedef iş yükü için — öngörülebilir, yüksek hacimli okumalar — evet. Maliyeti yazma anına taşırsınız (ve bir miktar veri tekrarını kabul edersiniz) ve karşılığında düz ölçeklenen tek istekli okumalar elde edersiniz. Tek tablo tasarımı kılavuzu takasları kapsar.
Bu okumalar için anahtarları ve koşulları elle oluşturmak zahmetlidir —
ifade oluşturucu sizin için
KeyConditionExpression / FilterExpression söz dizimini üretir ve
DynoTable bir geçici çözüm yetmediğinde gerçek SQL'i çalıştırır.