Översikt över indexering i Azure Cosmos DB
GÄLLER FÖR: NoSQL MongoDB Kassandra Gremlin Bord
Azure Cosmos DB är en schemaagnostisk databas som gör att du kan iterera i ditt program utan att behöva hantera schema- eller indexhantering. Som standard indexerar Azure Cosmos DB automatiskt varje egenskap för alla objekt i containern utan att behöva definiera något schema eller konfigurera sekundära index.
Målet med den här artikeln är att förklara hur Azure Cosmos DB indexerar data samt hur det använder index för att förbättra frågeprestanda. Vi rekommenderar att du går igenom det här avsnittet innan du utforskar hur du anpassar indexeringsprinciper.
Varje gång ett objekt lagras i en container projiceras innehållet som ett JSON-dokument och konverteras sedan till en trädrepresentation. Den här konverteringen innebär att varje egenskap för objektet representeras som en nod i ett träd. En pseudorotnod skapas som överordnad till alla egenskaper på första nivån för objektet. Lövnoderna innehåller de faktiska skalärvärdena som bärs av ett objekt.
Tänk till exempel på det här objektet:
{
"locations": [
{ "country": "Germany", "city": "Berlin" },
{ "country": "France", "city": "Paris" }
],
"headquarters": { "country": "Belgium", "employees": 250 },
"exports": [
{ "city": "Moscow" },
{ "city": "Athens" }
]
}
Det här trädet representerar JSON-exempelobjektet:
Observera hur matriser kodas i trädet: varje post i en matris får en mellanliggande nod märkt med indexet för den posten i matrisen (0, 1 osv.).
Anledningen till att Azure Cosmos DB omvandlar objekt till träd är att det gör att systemet kan referera till egenskaper med hjälp av deras sökvägar i dessa träd. För att hämta sökvägen för en egenskap kan vi bläddra i trädet från rotnoden till den egenskapen och sammanfoga etiketterna för varje bläddrade nod.
Här är sökvägarna för varje egenskap från exempelobjektet som beskrevs tidigare:
/locations/0/country
: "Tyskland"/locations/0/city
: "Berlin"/locations/1/country
: "Frankrike"/locations/1/city
: "Paris"/headquarters/country
: "Belgien"/headquarters/employees
: 250/exports/0/city
: "Moskva"/exports/1/city
: "Aten"
Azure Cosmos DB indexerar effektivt varje egenskaps sökväg och dess motsvarande värde när ett objekt skrivs.
Azure Cosmos DB stöder för närvarande tre typer av index. Du kan konfigurera dessa indextyper när du definierar indexeringsprincipen.
Intervallindex baseras på en ordnad trädliknande struktur. Intervallindextypen används för:
Likhetsfrågor:
SELECT * FROM container c WHERE c.property = 'value'
SELECT * FROM c WHERE c.property IN ("value1", "value2", "value3")
Likhetsmatchning för ett matriselement
SELECT * FROM c WHERE ARRAY_CONTAINS(c.tags, "tag1")
Intervallfrågor:
SELECT * FROM container c WHERE c.property > 'value'
Anteckning
(fungerar för
>
,<
,>=
,<=
,!=
)Söker efter förekomsten av en egenskap:
SELECT * FROM c WHERE IS_DEFINED(c.property)
Strängsystemfunktioner:
SELECT * FROM c WHERE CONTAINS(c.property, "value")
SELECT * FROM c WHERE STRINGEQUALS(c.property, "value")
ORDER BY
Frågor:SELECT * FROM container c ORDER BY c.property
JOIN
Frågor:SELECT child FROM container c JOIN child IN c.properties WHERE child = 'value'
Intervallindex kan användas på skalära värden (sträng eller tal). Standardprincipen för indexering för nyligen skapade containrar framtvingar intervallindex för strängar eller nummer. Information om hur du konfigurerar intervallindex finns i Principexempel för intervallindexering
Anteckning
En ORDER BY
sats som beställer efter en enskild egenskap behöver alltid ett intervallindex och misslyckas om sökvägen den refererar till inte har någon. På samma sätt behöver en ORDER BY
fråga som beställer efter flera egenskaper alltid ett sammansatt index.
Rumsliga index möjliggör effektiva frågor om geospatiala objekt som - punkter, linjer, polygoner och multipolygon. De här frågorna använder nyckelorden ST_DISTANCE, ST_WITHIN ST_INTERSECTS. Följande är några exempel som använder rumslig indextyp:
Geospatiala avståndsfrågor:
SELECT * FROM container c WHERE ST_DISTANCE(c.property, { "type": "Point", "coordinates": [0.0, 10.0] }) < 40
Geospatial i frågor:
SELECT * FROM container c WHERE ST_WITHIN(c.property, {"type": "Point", "coordinates": [0.0, 10.0] })
Geospatiala intersektera frågor:
SELECT * FROM c WHERE ST_INTERSECTS(c.property, { 'type':'Polygon', 'coordinates': [[ [31.8, -5], [32, -5], [31.8, -5] ]] })
Rumsliga index kan användas på korrekt formaterade GeoJSON-objekt . Punkter, LineStrings, Polygoner och MultiPolygons stöds för närvarande. Information om hur du konfigurerar rumsliga index finns i Exempel på spatial indexeringsprincip
Sammansatta index ökar effektiviteten när du utför åtgärder på flera fält. Den sammansatta indextypen används för:
ORDER BY
frågor om flera egenskaper:SELECT * FROM container c ORDER BY c.property1, c.property2
Frågor med ett filter och
ORDER BY
. Dessa frågor kan använda ett sammansatt index om filteregenskapen läggs till iORDER BY
-satsen.SELECT * FROM container c WHERE c.property1 = 'value' ORDER BY c.property1, c.property2
Frågor med ett filter på två eller flera egenskaper där minst en egenskap är ett likhetsfilter
SELECT * FROM container c WHERE c.property1 = 'value' AND c.property2 > 'value'
Så länge ett filterpredikat använder en av indextypen utvärderar frågemotorn det först innan resten genomsöks. Om du till exempel har en SQL-fråga, till exempel SELECT * FROM c WHERE c.firstName = "Andrew" and CONTAINS(c.lastName, "Liu")
Ovanstående fråga filtrerar först efter poster där firstName = "Andrew" med hjälp av indexet. Sedan skickas alla firstName = "Andrew"-poster via en efterföljande pipeline för att utvärdera CONTAINS-filterpredikatet.
Du kan påskynda frågor och undvika fullständiga containergenomsökningar när du använder funktioner som utför en fullständig genomsökning som CONTAINS. Du kan lägga till fler filterpredikat som använder indexet för att påskynda dessa frågor. Ordningen på filtersatser är inte viktig. Frågemotorn räknar ut vilka predikat som är mer selektiva och kör frågan därefter.
Information om hur du konfigurerar sammansatta index finns i Exempel på sammansatt indexeringsprincip
Vektorindex ökar effektiviteten när du utför vektorsökningar med hjälp av VectorDistance
systemfunktionen. Vektorsökningar har betydligt lägre svarstid, högre dataflöde och mindre RU-förbrukning vid användning av ett vektorindex.
Information om hur du konfigurerar vektorindex finns i principexempel för vektorindexering
ORDER BY
vektorsökningsfrågor:SELECT TOP 10 * FROM c ORDER BY VectorDistance(c.vector1, c.vector2)
Projektion av likhetspoängen i vektorsökningsfrågor:
SELECT TOP 10 c.name, VectorDistance(c.vector1, c.vector2) AS SimilarityScore FROM c ORDER BY VectorDistance(c.vector1, c.vector2)
Intervallfilter på likhetspoängen.
SELECT TOP 10 * FROM c WHERE VectorDistance(c.vector1, c.vector2) > 0.8 ORDER BY VectorDistance(c.vector1, c.vector2)
Viktigt
För närvarande är vektorprinciper och vektorindex oföränderliga när de har skapats. Skapa en ny samling för att göra ändringar.
Det finns fem sätt som frågemotorn kan utvärdera frågefilter, sorterade efter mest effektiva till minst effektiva:
- Indexsökning
- Exakt indexgenomsökning
- Utökad indexgenomsökning
- Fullständig indexgenomsökning
- Fullständig genomsökning
När du indexerar egenskapssökvägar använder frågemotorn automatiskt indexet så effektivt som möjligt. Förutom att indexera nya egenskapssökvägar behöver du inte konfigurera något för att optimera hur frågor använder indexet. En frågas RU-avgift är en kombination av både RU-avgiften från indexanvändningen och RU-avgiften från inläsning av objekt.
Här är en tabell som sammanfattar de olika sätt som index används i Azure Cosmos DB:
Indexsökningstyp | beskrivning | Vanliga exempel | RU-debitering från indexanvändning | RU-avgifter från inläsning av objekt från transaktionsdatalager |
---|---|---|---|---|
Indexsökning | Skrivskyddade obligatoriska indexerade värden och läs endast in matchande objekt från transaktionsdatalagret | Likhetsfilter, IN | Konstant per likhetsfilter | Ökningar baserat på antalet objekt i frågeresultat |
Exakt indexgenomsökning | Binär sökning efter indexerade värden och läs endast in matchande objekt från transaktionsdatalagret | Intervalljämförelser (>, <, <=eller >=), StartsWith | Jämförbar med indexsökning, ökar något baserat på kardinaliteten för indexerade egenskaper | Ökningar baserat på antalet objekt i frågeresultat |
Utökad indexgenomsökning | Optimerad sökning (men mindre effektiv än en binär sökning) av indexerade värden och läs endast in matchande objekt från transaktionsdatalagret | StartsWith (skiftlägesokänslig), StringEquals (skiftlägesokänslig) | Ökar något baserat på kardinaliteten för indexerade egenskaper | Ökningar baserat på antalet objekt i frågeresultat |
Fullständig indexgenomsökning | Läs distinkt uppsättning indexerade värden och läs endast in matchande objekt från transaktionsdatalagret | Contains, EndsWith, RegexMatch, LIKE | Ökar linjärt baserat på kardinaliteten för indexerade egenskaper | Ökningar baserat på antalet objekt i frågeresultat |
Fullständig genomsökning | Läs in alla objekt från transaktionsdatalagret | Övre, nedre | Ej tillämpligt | Ökningar baserat på antalet objekt i containern |
När du skriver frågor bör du använda filterpredikat som använder indexet så effektivt som möjligt. Om antingen StartsWith
eller Contains
skulle fungera för ditt användningsfall bör du till exempel välja eftersom det gör en exakt indexgenomsökning i stället för StartsWith
en fullständig indexgenomsökning.
I det här avsnittet går vi igenom mer information om hur frågor använder index. Den här detaljnivån är inte nödvändig för att lära dig att komma igång med Azure Cosmos DB, men den dokumenteras i detalj för nyfikna användare. Vi refererar till exempelobjektet som delades tidigare i det här dokumentet:
Exempelobjekt:
{
"id": 1,
"locations": [
{ "country": "Germany", "city": "Berlin" },
{ "country": "France", "city": "Paris" }
],
"headquarters": { "country": "Belgium", "employees": 250 },
"exports": [
{ "city": "Moscow" },
{ "city": "Athens" }
]
}
{
"id": 2,
"locations": [
{ "country": "Ireland", "city": "Dublin" }
],
"headquarters": { "country": "Belgium", "employees": 200 },
"exports": [
{ "city": "Moscow" },
{ "city": "Athens" },
{ "city": "London" }
]
}
Azure Cosmos DB använder ett inverterat index. Indexet fungerar genom att mappa varje JSON-sökväg till den uppsättning objekt som innehåller det värdet. Objekt-ID-mappningen representeras på många olika indexsidor för containern. Här är ett exempeldiagram över ett inverterat index för en container som innehåller de två exempelobjekten:
Sökväg | Värde | Lista över objekt-ID:t |
---|---|---|
/locations/0/country | Tyskland | 1 |
/locations/0/country | Irland | 2 |
/locations/0/city | Berlin | 1 |
/locations/0/city | Dublin | 2 |
/locations/1/country | Frankrike | 1 |
/locations/1/city | Paris | 1 |
/huvudkontor/land | Belgien | 1, 2 |
/huvudkontor/anställda | 200 | 2 |
/huvudkontor/anställda | 250 | 1 |
Det inverterade indexet har två viktiga attribut:
- För en viss sökväg sorteras värdena i stigande ordning. Därför kan frågemotorn enkelt fungera
ORDER BY
från indexet. - För en viss sökväg kan frågemotorn söka igenom den distinkta uppsättningen möjliga värden för att identifiera indexsidorna där det finns resultat.
Frågemotorn kan använda det inverterade indexet på fyra olika sätt:
Överväg följande fråga:
SELECT location
FROM location IN company.locations
WHERE location.country = 'France'
Frågepredikatet (filtrering på objekt där en plats har "Frankrike" som land/region) skulle matcha sökvägen som är markerad i det här diagrammet:
Eftersom den här frågan har ett likhetsfilter kan vi snabbt identifiera de indexsidor som innehåller frågeresultaten när vi har gått igenom det här trädet. I det här fallet skulle frågemotorn läsa indexsidor som innehåller objekt 1. En indexsökning är det mest effektiva sättet att använda indexet. Med en indexsökning läser vi bara de nödvändiga indexsidorna och läser bara in objekten i frågeresultatet. Därför är indexuppslagstiden och RU-avgiften från indexsökningen otroligt låg, oavsett den totala datavolymen.
Överväg följande fråga:
SELECT *
FROM company
WHERE company.headquarters.employees > 200
Frågepredikatet (filtrering på objekt där det finns fler än 200 anställda) kan utvärderas med en exakt indexgenomsökning av headquarters/employees
sökvägen. När du gör en exakt indexgenomsökning börjar frågemotorn med en binär sökning av den distinkta uppsättningen möjliga värden för att hitta platsen för värdet 200
för headquarters/employees
sökvägen. Eftersom värdena för varje sökväg sorteras i stigande ordning är det enkelt för frågemotorn att göra en binär sökning. När frågemotorn har hittat värdet 200
börjar den läsa alla återstående indexsidor (i stigande riktning).
Eftersom frågemotorn kan göra en binär sökning för att undvika genomsökning av onödiga indexsidor tenderar exakta indexgenomsökningar att ha jämförbara svarstider och RU-avgifter för indexsökningsåtgärder.
Överväg följande fråga:
SELECT *
FROM company
WHERE STARTSWITH(company.headquarters.country, "United", true)
Frågepredikatet (filtrering på objekt som har huvudkontor på en plats som börjar med skiftlägesokänsliga "United") kan utvärderas med en utökad indexgenomsökning av headquarters/country
sökvägen. Åtgärder som utför en utökad indexgenomsökning har optimeringar som kan hjälpa dig att undvika behov av att söka igenom varje indexsida, men som är något dyrare än en exakt indexgenomsöknings binär sökning.
När du till exempel utvärderar skiftlägesokänsligt StartsWith
kontrollerar frågemotorn indexet efter olika möjliga kombinationer av versaler och gemener. Med den här optimeringen kan frågemotorn undvika att läsa de flesta indexsidor. Olika systemfunktioner har olika optimeringar som de kan använda för att undvika att läsa varje indexsida, så de kategoriseras i stort sett som expanderad indexgenomsökning.
Överväg följande fråga:
SELECT *
FROM company
WHERE CONTAINS(company.headquarters.country, "United")
Frågepredikatet (filtrering på objekt som har huvudkontor på en plats som innehåller "United") kan utvärderas med en indexgenomsökning av headquarters/country
sökvägen. Till skillnad från en exakt indexgenomsökning genomsöker en fullständig indexgenomsökning alltid genom den distinkta uppsättningen möjliga värden för att identifiera indexsidorna där det finns resultat. I det här fallet Contains
körs på indexet. Indexsökningstiden och RU-avgiften för indexgenomsökningar ökar när sökvägens kardinalitet ökar. Med andra ord, ju mer möjliga distinkta värden som frågemotorn behöver genomsöka, desto högre svarstid och RU-debitering ingår i en fullständig indexgenomsökning.
Överväg till exempel två egenskaper: town
och country
. Kardinaliteten av townen är 5.000 och kardinaliteten av country
är 200. Här är två exempelfrågor som var och en har en Contains-systemfunktion som gör en fullständig indexgenomsökning på town
egenskapen. Den första frågan använder fler RU:er än den andra frågan eftersom kardinaliteten i staden är högre än country
.
SELECT *
FROM c
WHERE CONTAINS(c.town, "Red", false)
SELECT *
FROM c
WHERE CONTAINS(c.country, "States", false)
I vissa fall kanske frågemotorn inte kan utvärdera ett frågefilter med hjälp av indexet. I det här fallet måste frågemotorn läsa in alla objekt från transaktionsarkivet för att kunna utvärdera frågefiltret. Fullständiga genomsökningar använder inte indexet och har en RU-avgift som ökar linjärt med den totala datastorleken. Lyckligtvis är åtgärder som kräver fullständiga genomsökningar sällsynta.
Om du inte definierar en vektorindexprincip och använder VectorDistance
systemfunktionen i en ORDER BY
-sats resulterar detta i en fullständig genomsökning och har en RU-avgift som är högre än om du har definierat en vektorindexprincip. Likhet, om du använder VectorDistance med det booleska brute force-värdet inställt på true
, och inte har något flat
index definierat för vektorsökvägen, kommer en fullständig genomsökning att ske.
I de tidigare exemplen övervägde vi bara frågor som hade enkla filteruttryck (till exempel frågor med bara ett enda likhets- eller intervallfilter). I verkligheten har de flesta frågor mycket mer komplexa filteruttryck.
Överväg följande fråga:
SELECT *
FROM company
WHERE company.headquarters.employees = 200 AND CONTAINS(company.headquarters.country, "United")
För att köra den här frågan måste frågemotorn göra en indexsökning på headquarters/employees
och en fullständig indexgenomsökning på headquarters/country
. Frågemotorn har interna heuristiker som används för att utvärdera frågefilteruttrycket så effektivt som möjligt. I det här fallet skulle frågemotorn undvika att behöva läsa onödiga indexsidor genom att göra indexsökningen först. Om till exempel endast 50 objekt matchade likhetsfiltret skulle frågemotorn bara behöva utvärdera Contains
på indexsidorna som innehöll dessa 50 objekt. En fullständig indexgenomsökning av hela containern skulle inte vara nödvändig.
Frågor med aggregerade funktioner måste uteslutande förlita sig på indexet för att kunna använda det.
I vissa fall kan indexet returnera falska positiva identifieringar. När du till exempel utvärderar Contains
indexet kan antalet matchningar i indexet överskrida antalet frågeresultat. Frågemotorn läser in alla indexmatchningar, utvärderar filtret på de inlästa objekten och returnerar endast rätt resultat.
För de flesta frågor har inläsning av falska positiva indexmatchningar ingen märkbar effekt på indexanvändningen.
Tänk dig följande fråga:
SELECT *
FROM company
WHERE CONTAINS(company.headquarters.country, "United")
Systemfunktionen Contains
kan returnera några falska positiva matchningar, så frågemotorn måste kontrollera om varje inläst objekt matchar filteruttrycket. I det här exemplet kanske frågemotorn bara behöver läsa in ett extra fåtal objekt, så effekten på indexanvändningen och RU-avgiften är minimal.
Frågor med aggregerade funktioner måste dock uteslutande förlita sig på indexet för att kunna använda det. Tänk till exempel på följande fråga med en Count
aggregering:
SELECT COUNT(1)
FROM company
WHERE CONTAINS(company.headquarters.country, "United")
Precis som i det första exemplet Contains
kan systemfunktionen returnera några falska positiva matchningar. Till skillnad från frågan SELECT *
kan frågan dock Count
inte utvärdera filteruttrycket på de inlästa objekten för att verifiera alla indexmatchningar. Frågan Count
måste uteslutande förlita sig på indexet, så om det finns en chans att ett filteruttryck returnerar falska positiva matchningar, tillgriper frågemotorn en fullständig genomsökning.
Frågor med följande aggregeringsfunktioner måste endast förlita sig på indexet, så för att utvärdera vissa systemfunktioner krävs en fullständig genomsökning.
Läs mer om indexering i följande artiklar: