sql >> Database >  >> RDS >> Database

De voordelen van het indexeren van buitenlandse sleutels

Primaire en externe sleutels zijn fundamentele kenmerken van relationele databases, zoals oorspronkelijk vermeld in het artikel van E.F. Codd, "A Relational Model of Data for Large Shared Data Banks", gepubliceerd in 1970. Het vaak herhaalde citaat is:"The key, the whole key, en niets anders dan de sleutel, dus help me Codd."

Achtergrond:primaire toetsen

Een primaire sleutel is een beperking in SQL Server, die elke rij in een tabel op unieke wijze identificeert. De sleutel kan worden gedefinieerd als een enkele niet-NULL-kolom of een combinatie van niet-NULL-kolommen die een unieke waarde genereert en wordt gebruikt om entiteitsintegriteit voor een tabel af te dwingen. Een tabel kan slechts één primaire sleutel hebben en wanneer een primaire sleutelbeperking voor een tabel is gedefinieerd, wordt een unieke index gemaakt. Die index is standaard een geclusterde index, tenzij gespecificeerd als een niet-geclusterde index wanneer de primaire sleutelbeperking is gedefinieerd.

Overweeg de Sales.SalesOrderHeader tabel in de AdventureWorks2012 databank. Deze tabel bevat basisinformatie over een verkooporder, inclusief orderdatum en klant-ID, en elke verkoop wordt uniek geïdentificeerd door een SalesOrderID , wat de primaire sleutel voor de tabel is. Elke keer dat een nieuwe rij aan de tabel wordt toegevoegd, wordt de primaire sleutelbeperking (genaamd PK_SalesOrderHeader_SalesOrderID ) wordt gecontroleerd om er zeker van te zijn dat er al geen rij bestaat met dezelfde waarde voor SalesOrderID .

Buitenlandse sleutels

Gescheiden van primaire sleutels, maar zeer verwant, zijn externe sleutels. Een refererende sleutel is een kolom of combinatie van kolommen die hetzelfde is als de primaire sleutel, maar in een andere tabel. Buitenlandse sleutels worden gebruikt om een ​​relatie te definiëren en integriteit tussen twee tabellen af ​​te dwingen.

Om het bovengenoemde voorbeeld te blijven gebruiken, de SalesOrderID kolom bestaat als een externe sleutel in de Sales.SalesOrderDetail tabel, waarin aanvullende informatie over de verkoop wordt opgeslagen, zoals product-ID en prijs. Wanneer een nieuwe verkoop wordt toegevoegd aan de SalesOrderHeader tabel, is het niet verplicht om een ​​rij voor die verkoop toe te voegen aan de SalesOrderDetail tabel  Wanneer u echter een rij toevoegt aan de SalesOrderDetail tabel, een corresponderende rij voor de SalesOrderID moet bestaan ​​in de SalesOrderHeader tafel.

Omgekeerd, bij het verwijderen van gegevens, een rij voor een specifieke SalesOrderID kan op elk moment worden verwijderd uit de SalesOrderDetail tabel, maar om een ​​rij te verwijderen uit de SalesOrderHeader tabel, bijbehorende rijen van SalesOrderDetail moet eerst worden verwijderd.

In tegenstelling tot primaire-sleutelbeperkingen, wanneer een externe-sleutelbeperking is gedefinieerd voor een tabel, wordt er standaard geen index gemaakt door SQL Server. Het is echter niet ongebruikelijk dat ontwikkelaars en databasebeheerders ze handmatig toevoegen. De externe sleutel kan deel uitmaken van een samengestelde primaire sleutel voor de tabel, in welk geval er een geclusterde index zou bestaan ​​met de externe sleutel als onderdeel van de clustersleutel. Als alternatief kunnen query's een index vereisen die de externe sleutel en een of meer extra kolommen in de tabel bevat, zodat er een niet-geclusterde index wordt gemaakt om die query's te ondersteunen. Verder kunnen indexen op externe sleutels prestatievoordelen bieden voor tabeljoins waarbij de primaire en externe sleutel betrokken zijn, en ze kunnen van invloed zijn op de prestaties wanneer de primaire sleutelwaarde wordt bijgewerkt of als de rij wordt verwijderd.

In de AdventureWorks2012 database, is er één tabel, SalesOrderDetail , met SalesOrderID als vreemde sleutel. Voor de SalesOrderDetail tabel, SalesOrderID en SalesOrderDetailID combineren om de primaire sleutel te vormen, ondersteund door een geclusterde index. Als de SalesOrderDetail tabel had geen index op de SalesOrderID kolom, en wanneer een rij wordt verwijderd uit SalesOrderHeader , zou SQL Server moeten verifiëren dat er geen rijen zijn voor dezelfde SalesOrderID waarde bestaan. Zonder indexen die de SalesOrderID . bevatten kolom, zou SQL Server een volledige tabelscan moeten uitvoeren van SalesOrderDetail . Zoals je je kunt voorstellen, hoe groter de tabel waarnaar wordt verwezen, hoe langer het verwijderen duurt.

Een voorbeeld

We kunnen dit zien in het volgende voorbeeld, waarin gebruik wordt gemaakt van kopieën van de bovengenoemde tabellen uit de AdventureWorks2012 database die zijn uitgebreid met een script dat hier te vinden is. Het script is ontwikkeld door Jonathan Kehayias (blog | @SQLPoolBoy) en creëert een SalesOrderHeaderEnlarged tabel met 1.258.600 rijen en een SalesOrderDetailEnlarged tafel met 4.852.680 rijen. Nadat het script was uitgevoerd, werd de externe-sleutelbeperking toegevoegd met behulp van de onderstaande instructies. Merk op dat de beperking is gemaakt met de ON DELETE CASCADE optie. Met deze optie, wanneer een update of verwijdering wordt uitgevoerd tegen de SalesOrderHeaderEnlarged tabel, rijen in de corresponderende tabel(len) – in dit geval gewoon SalesOrderDetailEnlarged – worden bijgewerkt of verwijderd.

Bovendien is de standaard, geclusterde index voor SalesOrderDetailEnglarged is verwijderd en opnieuw gemaakt om gewoon SalesOrderDetailID . te hebben als de primaire sleutel, omdat het een typisch ontwerp vertegenwoordigt.

USE [AdventureWorks2012];
GO
 
/* remove original clustered index */
ALTER TABLE [Sales].[SalesOrderDetailEnlarged] 
  DROP CONSTRAINT [PK_SalesOrderDetailEnlarged_SalesOrderID_SalesOrderDetailID];
GO
 
/* re-create clustered index with SalesOrderDetailID only */
ALTER TABLE [Sales].[SalesOrderDetailEnlarged] 
  ADD CONSTRAINT [PK_SalesOrderDetailEnlarged_SalesOrderDetailID] PRIMARY KEY CLUSTERED
  (
    [SalesOrderDetailID] ASC
  )
  WITH
  (
     PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, 
     IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON
  ) ON [PRIMARY];
GO
 
/* add foreign key constraint for SalesOrderID */
ALTER TABLE [Sales].[SalesOrderDetailEnlarged] WITH CHECK 
  ADD CONSTRAINT [FK_SalesOrderDetailEnlarged_SalesOrderHeaderEnlarged_SalesOrderID] 
  FOREIGN KEY([SalesOrderID])
  REFERENCES [Sales].[SalesOrderHeaderEnlarged] ([SalesOrderID])
  ON DELETE CASCADE;
GO
 
ALTER TABLE [Sales].[SalesOrderDetailEnlarged] 
  CHECK CONSTRAINT [FK_SalesOrderDetailEnlarged_SalesOrderHeaderEnlarged_SalesOrderID];
GO

Met de externe sleutelbeperking en zonder ondersteunende index, werd een enkele verwijdering uitgevoerd tegen de SalesOrderHeaderEnlarged tabel, wat resulteerde in de verwijdering van één rij uit SalesOrderHeaderEnlarged en 72 rijen van SalesOrderDetailEnlarged :

SET STATISTICS IO ON;
SET STATISTICS TIME ON;
 
DBCC DROPCLEANBUFFERS;
DBCC FREEPROCCACHE;
 
USE [AdventureWorks2012];
GO
 
DELETE FROM [Sales].[SalesOrderHeaderEnlarged] WHERE [SalesOrderID] = 292104;

De statistieken IO en timing informatie toonden het volgende:

SQL Server ontleden en compileren tijd:

CPU-tijd =8 ms, verstreken tijd =8 ms.

Tabel 'SalesOrderDetailEnlarged'. Scan count 1, logische leest 50647, fysieke leest 8, read-ahead leest 50667, lob logische leest 0, lob fysieke leest 0, lob read-ahead leest 0.
Tabel 'Werktafel'. Scantelling 2, logische leest 7, fysieke leest 0, read-ahead leest 0, lob logische leest 0, lob fysieke leest 0, lob read-ahead leest 0.
Tabel 'SalesOrderHeaderEnlarged'. Scantelling 0, logische leest 15, fysieke leest 14, read-ahead leest 0, lob logische leest 0, lob fysieke leest 0, lob read-ahead leest 0.

Uitvoeringstijden SQL Server:

CPU-tijd =1045 ms,  verstreken tijd =1898 ms.

Met behulp van SQL Sentry Plan Explorer toont het uitvoeringsplan een geclusterde indexscan tegen SalesOrderDetailEnlarged omdat er geen index is op SalesOrderID :


Queryplan zonder index op de externe sleutel

De niet-geclusterde index ter ondersteuning van SalesOrderDetailEnlarged werd vervolgens gemaakt met behulp van de volgende verklaring:

USE [AdventureWorks2012];
GO
 
/* create nonclustered index */
CREATE NONCLUSTERED INDEX [IX_SalesOrderDetailEnlarged_SalesOrderID] ON [Sales].[SalesOrderDetailEnlarged]
(
  [SalesOrderID] ASC
)
WITH
(
  PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, 
  ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON
)
ON [PRIMARY];
 
GO

Er is nog een verwijdering uitgevoerd voor een SalesOrderID die van invloed was op één rij in SalesOrderHeaderEnlarged en 72 rijen in SalesOrderDetailEnlarged :

SET STATISTICS IO ON;
SET STATISTICS TIME ON;
 
DBCC DROPCLEANBUFFERS;
DBCC FREEPROCCACHE;
 
USE [AdventureWorks2012];
GO
 
DELETE FROM [Sales].[SalesOrderHeaderEnlarged] WHERE [SalesOrderID] = 697505;

De IO-statistieken en timinginformatie lieten een dramatische verbetering zien:

SQL Server ontleden en compileren tijd:

CPU-tijd =0 ms, verstreken tijd =7 ms.

Tabel 'SalesOrderDetailEnlarged'. Scan count 1, logische leest 48, fysieke leest 13, read-ahead leest 0, lob logische leest 0, lob fysieke leest 0, lob read-ahead leest 0.
Tabel 'Werktabel'. Scantelling 2, logische leest 7, fysieke leest 0, read-ahead leest 0, lob logische leest 0, lob fysieke leest 0, lob read-ahead leest 0.
Tabel 'SalesOrderHeaderEnlarged'. Scantelling 0, logische leest 15, fysieke leest 15, read-ahead leest 0, lob logische leest 0, lob fysieke leest 0, lob read-ahead leest 0.

Uitvoeringstijden SQL Server:

CPU-tijd =0 ms,  verstreken tijd =27 ms.

En het zoekplan toonde een indexzoekopdracht van de niet-geclusterde index op SalesOrderID , zoals verwacht:


Queryplan met index op de externe sleutel

De uitvoeringstijd van de query daalde van 1898 ms naar 27 ms - een reductie van 98,58%, en leesbewerkingen voor de SalesOrderDetailEnlarged tafel daalde van 50647 naar 48 – een verbetering van 99,9%. Afgezien van de percentages, overweeg dan alleen de I/O die door het verwijderen wordt gegenereerd. De SalesOrderDetailEnlarged tabel is in dit voorbeeld slechts 500 MB, en voor een systeem met 256 GB beschikbaar geheugen lijkt een tabel die 500 MB in beslag neemt in de buffercache geen verschrikkelijke situatie. Maar een tabel van 5 miljoen rijen is relatief klein; de meeste grote OLTP-systemen hebben tabellen met honderden miljoenen rijen. Bovendien is het niet ongebruikelijk dat er meerdere refererende-sleutelverwijzingen bestaan ​​voor een primaire sleutel, waarbij het verwijderen van de primaire sleutel verwijderingen uit meerdere gerelateerde tabellen vereist. In dat geval is het mogelijk om langere duur voor verwijderingen te zien, wat niet alleen een prestatieprobleem is, maar ook een blokkeringsprobleem, afhankelijk van het isolatieniveau.

Conclusie

Het wordt over het algemeen aanbevolen om een ​​index te maken die leidt naar de externe sleutelkolom(men), om niet alleen joins tussen de primaire en externe sleutels te ondersteunen, maar ook updates en verwijderingen. Merk op dat dit een algemene aanbeveling is, aangezien er randscenario's zijn waarbij de extra index op de refererende sleutel niet werd gebruikt vanwege de extreem kleine tabelgrootte, en de extra indexupdates een negatieve invloed hadden op de prestaties. Zoals bij alle schemawijzigingen, moeten indextoevoegingen na implementatie worden getest en gecontroleerd. Het is belangrijk ervoor te zorgen dat de aanvullende indexen de gewenste effecten opleveren en geen negatieve invloed hebben op de prestaties van de oplossing. Het is ook vermeldenswaard hoeveel extra ruimte vereist is door de indexen voor de externe sleutels. Dit is essentieel om te overwegen voordat u de indexen maakt, en als ze een voordeel bieden, moet dit worden overwogen voor toekomstige capaciteitsplanning.


  1. Hoe kan ik een ddl-script genereren (of krijgen) op een bestaande tabel in Oracle? Ik moet ze opnieuw maken in Hive

  2. De meerdelige identifier kan niet worden gebonden

  3. Meerdere rijen samenvoegen en groeperen in Oracle

  4. Hoe u een gebruiker kunt maken met superuser-rechten in PostgreSQL