Overzicht van indexeren in Azure Cosmos DB
VAN TOEPASSING OP: Nosql MongoDB Cassandra Gremlin Tabel
Azure Cosmos DB is een schema-agnostische database waarmee u uw toepassing kunt herhalen zonder dat u te maken hebt met schema- of indexbeheer. Standaard indexeert Azure Cosmos DB automatisch elke eigenschap voor alle items in uw container zonder dat u een schema hoeft te definiëren of secundaire indexen hoeft te configureren.
Het doel van dit artikel is om uit te leggen hoe gegevens worden geïndexeerd met Azure Cosmos DB en hoe indexen worden gebruikt om de queryprestaties te verbeteren. Het is raadzaam deze sectie te doorlopen voordat u het indexeringsbeleid aanpast.
Van items naar structuren
Telkens wanneer een item wordt opgeslagen in een container, wordt de inhoud ervan geprojecteerd als een JSON-document en vervolgens geconverteerd naar een structuurweergave. Deze conversie betekent dat elke eigenschap van dat item wordt weergegeven als een knooppunt in een structuur. Een pseudo-hoofdknooppunt wordt gemaakt als een bovenliggend knooppunt voor alle eigenschappen op het eerste niveau van het item. De leaf-knooppunten bevatten de werkelijke scalaire waarden die door een item worden gedragen.
Als voorbeeld kunt u het volgende item bekijken:
{
"locations": [
{ "country": "Germany", "city": "Berlin" },
{ "country": "France", "city": "Paris" }
],
"headquarters": { "country": "Belgium", "employees": 250 },
"exports": [
{ "city": "Moscow" },
{ "city": "Athens" }
]
}
Deze structuur vertegenwoordigt het JSON-voorbeelditem:
U ziet hoe matrices worden gecodeerd in de structuur: elke vermelding in een matrix krijgt een tussenliggend knooppunt met het label van de index van die vermelding in de matrix (0, 1 enzovoort).
Van bomen naar eigenschapspaden
De reden waarom Azure Cosmos DB items transformeert in structuren, is omdat het systeem hiermee naar eigenschappen kan verwijzen met behulp van hun paden binnen die structuren. Om het pad voor een eigenschap op te halen, kunnen we de structuur van het hoofdknooppunt naar die eigenschap doorkruisen en de labels van elk doorkruist knooppunt samenvoegen.
Dit zijn de paden voor elke eigenschap uit het voorbeelditem dat eerder is beschreven:
/locations/0/country
: "Duitsland"/locations/0/city
: "Berlijn"/locations/1/country
: "Frankrijk"/locations/1/city
: "Parijs"/headquarters/country
: "België"/headquarters/employees
: 250/exports/0/city
: "Moskou"/exports/1/city
: "Athene"
Azure Cosmos DB indexeert effectief het pad van elke eigenschap en de bijbehorende waarde wanneer een item wordt geschreven.
Typen indexen
Azure Cosmos DB ondersteunt momenteel drie typen indexen. U kunt deze indextypen configureren bij het definiëren van het indexeringsbeleid.
Bereikindex
Bereikindexen zijn gebaseerd op een geordende structuur die lijkt op een structuur. Het type bereikindex wordt gebruikt voor:
Gelijkheidsquery's:
SELECT * FROM container c WHERE c.property = 'value'
SELECT * FROM c WHERE c.property IN ("value1", "value2", "value3")
Gelijkheidsmatch voor een matrixelement
SELECT * FROM c WHERE ARRAY_CONTAINS(c.tags, "tag1")
Bereikquery's:
SELECT * FROM container c WHERE c.property > 'value'
Notitie
(werkt voor
>
,<
,>=
,<=
, )!=
Controleren op de aanwezigheid van een eigenschap:
SELECT * FROM c WHERE IS_DEFINED(c.property)
Systeemfuncties voor tekenreeksen:
SELECT * FROM c WHERE CONTAINS(c.property, "value")
SELECT * FROM c WHERE STRINGEQUALS(c.property, "value")
ORDER BY
Query 's:SELECT * FROM container c ORDER BY c.property
JOIN
Query 's:SELECT child FROM container c JOIN child IN c.properties WHERE child = 'value'
Bereikindexen kunnen worden gebruikt voor scalaire waarden (tekenreeks of getal). Het standaardindexeringsbeleid voor nieuw gemaakte containers dwingt bereikindexen af voor een willekeurige tekenreeks of een willekeurig getal. Zie Voorbeelden van bereikindexeringsbeleid voor meer informatie over het configureren van bereikindexen
Notitie
Een ORDER BY
component die wordt georded op basis van één eigenschap, heeft altijd een bereikindex nodig en mislukt als het pad waarnaar wordt verwezen er geen heeft. Op dezelfde manier heeft een ORDER BY
query die op meerdere eigenschappen ordet , altijd een samengestelde index nodig.
Ruimtelijke index
Met ruimtelijke indexen kunt u efficiënte query's uitvoeren op georuimtelijke objecten, zoals punten, lijnen, veelhoeken en multipolygonen. Deze query's gebruiken ST_DISTANCE, ST_WITHIN ST_INTERSECTS trefwoorden. Hier volgen enkele voorbeelden die gebruikmaken van het type ruimtelijke index:
Query's op georuimtelijke afstand:
SELECT * FROM container c WHERE ST_DISTANCE(c.property, { "type": "Point", "coordinates": [0.0, 10.0] }) < 40
Georuimtelijk binnen query's:
SELECT * FROM container c WHERE ST_WITHIN(c.property, {"type": "Point", "coordinates": [0.0, 10.0] })
Georuimtelijke intersect-query's:
SELECT * FROM c WHERE ST_INTERSECTS(c.property, { 'type':'Polygon', 'coordinates': [[ [31.8, -5], [32, -5], [31.8, -5] ]] })
Ruimtelijke indexen kunnen worden gebruikt voor juist opgemaakte GeoJSON-objecten . Punten, lijntekenreeksen, veelhoeken en multipolygonen worden momenteel ondersteund. Zie Voorbeelden van beleid voor ruimtelijke indexering voor meer informatie over het configureren van ruimtelijke indexen
Samengestelde indexen
Samengestelde indexen verhogen de efficiëntie wanneer u bewerkingen uitvoert op meerdere velden. Het samengestelde indextype wordt gebruikt voor:
ORDER BY
query's uitvoeren op meerdere eigenschappen:SELECT * FROM container c ORDER BY c.property1, c.property2
Query's met een filter en
ORDER BY
. Deze query's kunnen gebruikmaken van een samengestelde index als de filtereigenschap wordt toegevoegd aan deORDER BY
component.SELECT * FROM container c WHERE c.property1 = 'value' ORDER BY c.property1, c.property2
Query's met een filter op twee of meer eigenschappen waarbij ten minste één eigenschap een gelijkheidsfilter is
SELECT * FROM container c WHERE c.property1 = 'value' AND c.property2 > 'value'
Zolang één filterpredicaat een van het indextype gebruikt, evalueert de query-engine dat eerst voordat de rest wordt gescand. Als u bijvoorbeeld een SQL-query hebt, zoals SELECT * FROM c WHERE c.firstName = "Andrew" and CONTAINS(c.lastName, "Liu")
De bovenstaande query filtert eerst op vermeldingen waarbij firstName = "Andrew" met behulp van de index. Vervolgens worden alle firstName = 'Andrew'-vermeldingen doorgegeven via een volgende pijplijn om het predicaat CONTAINS-filter te evalueren.
U kunt query's versnellen en volledige containerscans voorkomen wanneer u functies gebruikt die een volledige scan uitvoeren, zoals CONTAINS. U kunt meer filterpredicaten toevoegen die de index gebruiken om deze query's te versnellen. De volgorde van filtercomponenten is niet belangrijk. De query-engine zoekt uit welke predicaten selectiever zijn en voer de query dienovereenkomstig uit.
Zie Voorbeelden van samengesteld indexeringsbeleid voor meer informatie over het configureren van samengestelde indexen
Indexgebruik
Er zijn vijf manieren waarop de query-engine queryfilters kan evalueren, gesorteerd op meest efficiënt naar minst efficiënt:
- Indexzoeken
- Nauwkeurige indexscan
- Uitgebreide indexscan
- Volledige indexscan
- Volledige scan
Wanneer u eigenschapspaden indexeren, maakt de query-engine automatisch zo efficiënt mogelijk gebruik van de index. Afgezien van het indexeren van nieuwe eigenschapspaden, hoeft u niets te configureren om te optimaliseren hoe query's de index gebruiken. De RU-kosten van een query zijn een combinatie van zowel de RU-kosten uit indexgebruik als de RU-kosten voor het laden van items.
Hier volgt een tabel met een overzicht van de verschillende manieren waarop indexen worden gebruikt in Azure Cosmos DB:
Type indexzoekactie | Description | Algemene voorbeelden | RU-kosten uit indexgebruik | RU-kosten voor het laden van items uit transactioneel gegevensarchief |
---|---|---|---|---|
Indexzoeken | Alleen vereiste geïndexeerde waarden lezen en alleen overeenkomende items uit het transactionele gegevensarchief laden | Gelijkheidsfilters, IN | Constante per gelijkheidsfilter | Verhogingen op basis van het aantal items in queryresultaten |
Nauwkeurige indexscan | Binair zoeken naar geïndexeerde waarden en alleen overeenkomende items laden uit het transactionele gegevensarchief | Bereikvergelijkingen (>, <, <=of >=), StartsWith | Vergelijkbaar met indexzoeken, neemt licht toe op basis van de kardinaliteit van geïndexeerde eigenschappen | Verhogingen op basis van het aantal items in queryresultaten |
Uitgebreide indexscan | Geoptimaliseerd zoeken (maar minder efficiënt dan een binaire zoekopdracht) van geïndexeerde waarden en alleen overeenkomende items laden uit het transactionele gegevensarchief | StartsWith (niet hoofdlettergevoelig), StringEquals (niet hoofdlettergevoelig) | Neemt licht toe op basis van de kardinaliteit van geïndexeerde eigenschappen | Verhogingen op basis van het aantal items in queryresultaten |
Volledige indexscan | Afzonderlijke set geïndexeerde waarden lezen en alleen overeenkomende items uit het transactionele gegevensarchief laden | Contains, EndsWith, RegexMatch, LIKE | Lineair verhoogd op basis van de kardinaliteit van geïndexeerde eigenschappen | Verhogingen op basis van het aantal items in queryresultaten |
Volledige scan | Alle items uit het transactionele gegevensarchief laden | Boven, Onder | N.v.t. | Verhogingen op basis van het aantal items in de container |
Bij het schrijven van query's moet u filterpredicaten gebruiken die de index zo efficiënt mogelijk gebruiken. Als een of Contains
bijvoorbeeld StartsWith
werkt voor uw use-case, moet u ervoor kiezen StartsWith
omdat hiermee een nauwkeurige indexscan wordt uitgevoerd in plaats van een volledige indexscan.
Details van indexgebruik
In deze sectie wordt meer informatie behandeld over hoe query's indexen gebruiken. Dit detailniveau is niet nodig om aan de slag te gaan met Azure Cosmos DB, maar wordt gedetailleerd gedocumenteerd voor nieuwsgierige gebruikers. We verwijzen naar het voorbeelditem dat eerder in dit document is gedeeld:
Voorbeelditems:
{
"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 maakt gebruik van een omgekeerde index. De index werkt door elk JSON-pad toe te koppelen aan de set items die die waarde bevatten. De toewijzing van de item-id wordt weergegeven op veel verschillende indexpagina's voor de container. Hier volgt een voorbeelddiagram van een omgekeerde index voor een container met de twee voorbeelditems:
Pad | Waarde | Lijst met item-id's |
---|---|---|
/locations/0/country | Duitsland | 1 |
/locations/0/country | Ierland | 2 |
/locations/0/city | Berlijn | 1 |
/locations/0/city | Dublin | 2 |
/locaties/1/land | Frankrijk | 1 |
/locations/1/city | Parijs | 1 |
/hoofdkantoor/land | België | 1, 2 |
/hoofdkantoor/werknemers | 200 | 2 |
/hoofdkantoor/werknemers | 250 | 1 |
De omgekeerde index heeft twee belangrijke kenmerken:
- Voor een bepaald pad worden waarden in oplopende volgorde gesorteerd. Daarom kan de query-engine eenvoudig worden uitgevoerd
ORDER BY
vanuit de index. - Voor een bepaald pad kan de query-engine de afzonderlijke set mogelijke waarden scannen om de indexpagina's met resultaten te identificeren.
De query-engine kan de omgekeerde index op vier verschillende manieren gebruiken:
Indexzoeken
Laten we nu eens naar deze query kijken:
SELECT location
FROM location IN company.locations
WHERE location.country = 'France'`
Het querypredicaat (filteren op items waarbij een locatie 'Frankrijk' als land/regio heeft) komt overeen met het pad dat in dit diagram is gemarkeerd:
Omdat deze query een gelijkheidsfilter heeft, kunnen we na het doorlopen van deze structuur snel de indexpagina's identificeren die de queryresultaten bevatten. In dit geval leest de query-engine indexpagina's die Item 1 bevatten. Een indexzoekfunctie is de meest efficiënte manier om de index te gebruiken. Met een indexzoekactie lezen we alleen de benodigde indexpagina's en laden we alleen de items in de queryresultaten. Daarom zijn de opzoektijd van de index en de RU-kosten van indexzoekacties ongelooflijk laag, ongeacht het totale gegevensvolume.
Nauwkeurige indexscan
Laten we nu eens naar deze query kijken:
SELECT *
FROM company
WHERE company.headquarters.employees > 200
Het querypredicaat (filteren op items met meer dan 200 werknemers) kan worden geëvalueerd met een nauwkeurige indexscan van het headquarters/employees
pad. Wanneer u een nauwkeurige indexscan uitvoert, begint de query-engine met een binaire zoekopdracht van de afzonderlijke set mogelijke waarden om de locatie van de waarde 200
voor het headquarters/employees
pad te vinden. Omdat de waarden voor elk pad in oplopende volgorde worden gesorteerd, kan de query-engine eenvoudig een binaire zoekopdracht uitvoeren. Nadat de query-engine de waarde 200
heeft gevonden, begint deze met het lezen van alle resterende indexpagina's (in oplopende richting).
Omdat de query-engine een binaire zoekopdracht kan uitvoeren om het scannen van onnodige indexpagina's te voorkomen, hebben nauwkeurige indexscans meestal vergelijkbare latentie en RU-kosten voor indexzoekbewerkingen.
Uitgebreide indexscan
Laten we nu eens naar deze query kijken:
SELECT *
FROM company
WHERE STARTSWITH(company.headquarters.country, "United", true)
Het querypredicaat (filteren op items die het hoofdkantoor hebben op een locatie die begint met niet-hoofdlettergevoelig 'United') kan worden geëvalueerd met een uitgebreide indexscan van het headquarters/country
pad. Bewerkingen die een uitgebreide indexscan uitvoeren, hebben optimalisaties die kunnen helpen voorkomen dat elke indexpagina moet worden gescand, maar zijn iets duurder dan de binaire zoekopdracht van een indexscan.
Als u bijvoorbeeld niet hoofdlettergevoelig StartsWith
evalueert, controleert de query-engine de index op verschillende mogelijke combinaties van hoofdletters en kleine letters. Dankzij deze optimalisatie kan de query-engine voorkomen dat de meeste indexpagina's worden gelezen. Verschillende systeemfuncties hebben verschillende optimalisaties die ze kunnen gebruiken om te voorkomen dat elke indexpagina wordt gelezen, zodat ze algemeen worden gecategoriseerd als uitgebreide indexscan.
Volledige indexscan
Laten we nu eens naar deze query kijken:
SELECT *
FROM company
WHERE CONTAINS(company.headquarters.country, "United")
Het querypredicaat (filteren op items met een hoofdkantoor op een locatie die 'Verenigd' bevat) kan worden geëvalueerd met een indexscan van het headquarters/country
pad. In tegenstelling tot een nauwkeurige indexscan scant een volledige indexscan altijd door de afzonderlijke set mogelijke waarden om de indexpagina's te identificeren met resultaten. In dit geval Contains
wordt uitgevoerd op de index. De tijd voor indexzoektijd en RU-kosten voor indexscans nemen toe naarmate de kardinaliteit van het pad toeneemt. Met andere woorden, hoe meer mogelijke afzonderlijke waarden de query-engine moet scannen, hoe hoger de latentie en ru-kosten die nodig zijn voor het uitvoeren van een volledige indexscan.
Denk bijvoorbeeld aan twee eigenschappen: town
en country
. De kardinaliteit van de stad is 5.000 en de kardinaliteit van country
is 200. Hier volgen twee voorbeeldquery's die elk een systeemfunctie bevatten waarmee een volledige indexscan op de town
eigenschap wordt uitgevoerd. De eerste query gebruikt meer RU's dan de tweede query omdat de kardinaliteit van plaats hoger is dan country
.
SELECT *
FROM c
WHERE CONTAINS(c.town, "Red", false)
SELECT *
FROM c
WHERE CONTAINS(c.country, "States", false)
Volledige scan
In sommige gevallen kan de query-engine een queryfilter mogelijk niet evalueren met behulp van de index. In dit geval moet de query-engine alle items uit de transactionele opslag laden om het queryfilter te kunnen evalueren. Volledige scans maken geen gebruik van de index en hebben een RU-kosten die lineair toenemen met de totale gegevensgrootte. Gelukkig zijn bewerkingen waarvoor volledige scans nodig zijn, zeldzaam.
Query's met complexe filterexpressies
In de eerdere voorbeelden hebben we alleen rekening gehouden met query's met eenvoudige filterexpressies (bijvoorbeeld query's met slechts één gelijkheids- of bereikfilter). In werkelijkheid hebben de meeste query's veel complexere filterexpressies.
Laten we nu eens naar deze query kijken:
SELECT *
FROM company
WHERE company.headquarters.employees = 200 AND CONTAINS(company.headquarters.country, "United")
Als u deze query wilt uitvoeren, moet de query-engine een indexzoekfunctie uitvoeren op headquarters/employees
en een volledige indexscan uitvoeren op headquarters/country
. De query-engine heeft interne heuristieken die worden gebruikt om de queryfilterexpressie zo efficiënt mogelijk te evalueren. In dit geval voorkomt de query-engine dat onnodige indexpagina's moeten worden gelezen door eerst de indexzoekactie uit te voeren. Als bijvoorbeeld slechts 50 items overeenkomen met het gelijkheidsfilter, hoeft de query-engine alleen te evalueren Contains
op de indexpagina's die deze 50 items bevatten. Een volledige indexscan van de hele container is niet nodig.
Indexgebruik voor scalaire statistische functies
Query's met statistische functies moeten uitsluitend afhankelijk zijn van de index om deze te kunnen gebruiken.
In sommige gevallen kan de index fout-positieven retourneren. Bij het evalueren Contains
van de index kan het aantal overeenkomsten in de index bijvoorbeeld groter zijn dan het aantal queryresultaten. De query-engine laadt alle indexovereenkomsten, evalueert het filter op de geladen items en retourneert alleen de juiste resultaten.
Voor de meeste query's heeft het laden van fout-positieve indexovereenkomsten geen merkbaar effect op het indexgebruik.
Kijk bijvoorbeeld eens naar de volgende query:
SELECT *
FROM company
WHERE CONTAINS(company.headquarters.country, "United")
De Contains
systeemfunctie kan enkele fout-positieve overeenkomsten retourneren, dus de query-engine moet controleren of elk geladen item overeenkomt met de filterexpressie. In dit voorbeeld hoeft de query-engine mogelijk slechts enkele extra items te laden, zodat het effect op het indexgebruik en de RU-kosten minimaal is.
Query's met statistische functies moeten echter uitsluitend afhankelijk zijn van de index om deze te kunnen gebruiken. Bekijk bijvoorbeeld de volgende query met een Count
aggregatie:
SELECT COUNT(1)
FROM company
WHERE CONTAINS(company.headquarters.country, "United")
Net als in het eerste voorbeeld kan de Contains
systeemfunctie enkele fout-positieve overeenkomsten retourneren. In tegenstelling tot de SELECT *
query kan de Count
query de filterexpressie voor de geladen items echter niet evalueren om alle indexovereenkomsten te controleren. De Count
query moet uitsluitend afhankelijk zijn van de index, dus als er een kans is dat een filterexpressie fout-positieve overeenkomsten retourneert, wordt een volledige scan uitgevoerd door de query-engine.
Query's met de volgende statistische functies moeten uitsluitend afhankelijk zijn van de index, dus voor het evalueren van sommige systeemfuncties is een volledige scan vereist.
Volgende stappen
Meer informatie over indexeren vindt u in de volgende artikelen: