Database
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Database

Lợi ích của việc lập chỉ mục các khóa nước ngoài

Khóa chính và khóa ngoài là đặc điểm cơ bản của cơ sở dữ liệu quan hệ, như được lưu ý ban đầu trong bài báo của E.F. Codd, “Mô hình dữ liệu quan hệ cho ngân hàng dữ liệu chia sẻ lớn”, được xuất bản vào năm 1970. Câu nói thường được lặp lại là, "Chìa khóa, toàn bộ khóa, và không có gì ngoài chìa khóa, vì vậy hãy giúp tôi Codd. "

Cơ sở:Các phím chính

Khóa chính là một ràng buộc trong SQL Server, có tác dụng xác định duy nhất từng hàng trong bảng. Khóa có thể được định nghĩa là một cột không phải NULL hoặc sự kết hợp của các cột không phải NULL tạo ra một giá trị duy nhất và được sử dụng để thực thi tính toàn vẹn của thực thể cho một bảng. Một bảng chỉ có thể có một khóa chính và khi một ràng buộc khóa chính được xác định cho một bảng, một chỉ mục duy nhất sẽ được tạo. Chỉ mục đó sẽ là một chỉ mục nhóm theo mặc định, trừ khi được chỉ định là chỉ mục không phân nhóm khi giới hạn khóa chính được xác định.

Hãy xem xét Sales.SalesOrderHeader trong AdventureWorks2012 cơ sở dữ liệu. Bảng này chứa thông tin cơ bản về đơn đặt hàng, bao gồm ngày đặt hàng và ID khách hàng, và mỗi lần bán hàng được xác định duy nhất bởi một SalesOrderID , là khóa chính của bảng. Mỗi khi một hàng mới được thêm vào bảng, ràng buộc khóa chính (có tên là PK_SalesOrderHeader_SalesOrderID ) được kiểm tra để đảm bảo rằng không có hàng nào tồn tại có cùng giá trị cho SalesOrderID .

Phím ngoại

Tách biệt với khóa chính, nhưng có liên quan rất nhiều, là khóa ngoại. Khóa ngoại là một cột hoặc tổ hợp các cột giống với khóa chính nhưng nằm trong một bảng khác. Khóa ngoại được sử dụng để xác định mối quan hệ và thực thi tính toàn vẹn giữa hai bảng.

Để tiếp tục sử dụng ví dụ nói trên, SalesOrderID tồn tại dưới dạng khóa ngoại trong Sales.SalesOrderDetail bảng, nơi lưu trữ thông tin bổ sung về chương trình giảm giá, chẳng hạn như ID sản phẩm và giá. Khi chương trình giảm giá mới được thêm vào SalesOrderHeader bảng, không bắt buộc phải thêm hàng cho đợt giảm giá đó vào SalesOrderDetail bảng Tuy nhiên, khi thêm một hàng vào SalesOrderDetail bảng, một hàng tương ứng cho SalesOrderID phải tồn tại trong SalesOrderHeader bảng.

Ngược lại, khi xóa dữ liệu, một hàng cho một SalesOrderID cụ thể có thể bị xóa bất kỳ lúc nào khỏi SalesOrderDetail nhưng để xóa một hàng khỏi SalesOrderHeader bảng, các hàng được liên kết từ SalesOrderDetail sẽ cần được xóa trước.

Không giống như các ràng buộc khóa chính, khi một ràng buộc khóa ngoại được xác định cho một bảng, một chỉ mục không được SQL Server tạo theo mặc định. Tuy nhiên, không có gì lạ khi các nhà phát triển và quản trị viên cơ sở dữ liệu thêm chúng theo cách thủ công. Khóa ngoại có thể là một phần của khóa chính tổng hợp cho bảng, trong trường hợp đó, một chỉ mục được phân cụm sẽ tồn tại cùng với khóa ngoại như một phần của khóa phân cụm. Ngoài ra, các truy vấn có thể yêu cầu một chỉ mục bao gồm khóa ngoại và một hoặc nhiều cột bổ sung trong bảng, do đó, một chỉ mục không phân biệt sẽ được tạo để hỗ trợ các truy vấn đó. Hơn nữa, các chỉ mục trên khóa ngoại có thể mang lại lợi ích về hiệu suất cho các phép nối bảng liên quan đến khóa chính và khóa ngoại và chúng có thể ảnh hưởng đến hiệu suất khi giá trị khóa chính được cập nhật hoặc nếu hàng bị xóa.

Trong AdventureWorks2012 cơ sở dữ liệu, có một bảng, SalesOrderDetail , với SalesOrderID như một khóa ngoại. Đối với SalesOrderDetail bảng, SalesOrderIDSalesOrderDetailID kết hợp để tạo thành khóa chính, được hỗ trợ bởi một chỉ mục được phân cụm. Nếu SalesOrderDetail bảng không có chỉ mục trên SalesOrderID , sau đó khi một hàng bị xóa khỏi SalesOrderHeader , SQL Server sẽ phải xác minh rằng không có hàng nào cho cùng một SalesOrderID giá trị tồn tại. Không có bất kỳ chỉ mục nào chứa SalesOrderID , SQL Server sẽ cần thực hiện quét toàn bộ bảng của SalesOrderDetail . Như bạn có thể tưởng tượng, bảng được tham chiếu càng lớn, quá trình xóa sẽ mất nhiều thời gian hơn.

Một ví dụ

Chúng ta có thể thấy điều này trong ví dụ sau, sử dụng các bản sao của các bảng nói trên từ AdventureWorks2012 cơ sở dữ liệu đã được mở rộng bằng cách sử dụng một tập lệnh có thể được tìm thấy tại đây. Tập lệnh được phát triển bởi Jonathan Kehayias (blog | @SQLPoolBoy) và tạo ra một SalesOrderHeaderEnlarged bảng có 1.258.600 hàng và SalesOrderDetailEnlarged bảng với 4,852,680 hàng. Sau khi tập lệnh được chạy, ràng buộc khóa ngoại đã được thêm vào bằng cách sử dụng các câu lệnh bên dưới. Lưu ý rằng ràng buộc được tạo bằng ON DELETE CASCADE lựa chọn. Với tùy chọn này, khi một bản cập nhật hoặc xóa được phát hành đối với SalesOrderHeaderEnlarged bảng, các hàng trong (các) bảng tương ứng - trong trường hợp này chỉ là SalesOrderDetailEnlarged - được cập nhật hoặc bị xóa.

Ngoài ra, chỉ mục nhóm, mặc định cho SalesOrderDetailEnglarged đã bị loại bỏ và được tạo lại để chỉ có SalesOrderDetailID làm khóa chính, vì nó đại diện cho một thiết kế điển hình.

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

Với ràng buộc khóa ngoại và không có chỉ mục hỗ trợ, một lần xóa đã được thực hiện đối với SalesOrderHeaderEnlarged , dẫn đến việc xóa một hàng khỏi SalesOrderHeaderEnlarged và 72 hàng từ SalesOrderDetailEnlarged :

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

Thống kê IO và thông tin thời gian cho thấy những điều sau:

Thời gian biên dịch và phân tích cú pháp SQL Server:

Thời gian CPU =8 ms, thời gian trôi qua =8 ms.

Bảng 'SalesOrderDetailEnlarged'. Quét đếm 1, đọc logic 50647, đọc vật lý 8, đọc trước 50667, đọc logic lob 0, đọc vật lý lob 0, đọc trước lob đọc 0.
Bảng 'Bàn làm việc'. Quét đếm 2, đọc lôgic 7, đọc vật lý 0, đọc trước đọc 0, lôgic đọc 0, thu đọc vật lý 0, đọc trước tiểu giải đọc 0.
Bảng 'SalesOrderHeaderEnlarged'. Lượt quét 0, lượt đọc logic 15, lượt đọc vật lý 14, lượt đọc trước là 0, lượt đọc logic 0, lượt đọc vật lý là 0, lượt đọc về phía trước của lượt đọc là 0.

Thời gian thực thi SQL Server:

Thời gian CPU =1045 mili giây, thời gian đã trôi qua =1898 mili giây.

Sử dụng SQL Sentry Plan Explorer, kế hoạch thực thi hiển thị quét chỉ mục theo cụm đối với SalesOrderDetailEnlarged vì không có chỉ mục nào trên SalesOrderID :


Kế hoạch truy vấn không có chỉ mục trên khóa ngoại

Chỉ mục không phân biệt để hỗ trợ SalesOrderDetailEnlarged sau đó được tạo bằng câu lệnh sau:

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

Một lần xóa khác đã được thực hiện cho một SalesOrderID đã ảnh hưởng đến một hàng trong SalesOrderHeaderEnlarged và 72 hàng trong SalesOrderDetailEnlarged :

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

Thống kê IO và thông tin thời gian cho thấy một sự cải thiện đáng kể:

Thời gian biên dịch và phân tích cú pháp SQL Server:

Thời gian CPU =0 ms, thời gian trôi qua =7 ms.

Bảng 'SalesOrderDetailEnlarged'. Số lần quét 1, số lần đọc logic 48, số lần đọc vật lý 13, số đọc trước đọc 0, số lần đọc logic số 0, số lần đọc số phận vật lý 0, số lần đọc trước số đo 0.
Bảng 'Bàn làm việc'. Quét đếm 2, đọc lôgic 7, đọc vật lý 0, đọc trước đọc 0, lôgic đọc 0, thu đọc vật lý 0, đọc trước tiểu giải đọc 0.
Bảng 'SalesOrderHeaderEnlarged'. Quét đếm 0, đọc lôgic 15, đọc vật lý 15, đọc trước đọc 0, đọc lôgic 0, ghi lôgic vật lý 0, đọc trước lôgic đọc 0.

Thời gian thực thi SQL Server:

Thời gian CPU =0 ms, thời gian trôi qua =27 ms.

Và kế hoạch truy vấn cho thấy một tìm kiếm chỉ mục của chỉ mục không phân biệt trên SalesOrderID , như mong đợi:


Kế hoạch truy vấn có chỉ mục trên khóa ngoại

Thời gian thực hiện truy vấn giảm từ 1898 mili giây xuống 27 mili giây - giảm 98,58% và đọc cho SalesOrderDetailEnlarged bảng giảm từ 50647 xuống 48 - cải thiện 99,9%. Phần phần trăm sang một bên, hãy xem xét I / O một mình được tạo ra bởi quá trình xóa. SalesOrderDetailEnlarged bảng chỉ có 500 MB trong ví dụ này và đối với hệ thống có 256 GB bộ nhớ khả dụng, một bảng chiếm 500 MB trong bộ đệm đệm có vẻ không phải là một tình huống tồi tệ. Nhưng một bảng 5 triệu hàng là tương đối nhỏ; hầu hết các hệ thống OLTP lớn đều có bảng với hàng trăm triệu hàng. Ngoài ra, không có gì lạ khi nhiều tham chiếu khóa ngoại tồn tại cho một khóa chính, trong đó việc xóa khóa chính yêu cầu xóa khỏi nhiều bảng có liên quan. Trong trường hợp đó, có thể thấy thời lượng xóa kéo dài không chỉ là vấn đề hiệu suất mà còn là vấn đề chặn, tùy thuộc vào mức độ cô lập.

Kết luận

Thông thường, bạn nên tạo một chỉ mục dẫn đầu trên (các) cột khóa ngoại, để hỗ trợ không chỉ các phép nối giữa khóa chính và khóa ngoài, mà còn hỗ trợ cập nhật và xóa. Lưu ý rằng đây là khuyến nghị chung, vì có những tình huống biên trong đó chỉ mục bổ sung trên khóa ngoại không được sử dụng do kích thước bảng cực kỳ nhỏ và các cập nhật chỉ mục bổ sung thực sự ảnh hưởng tiêu cực đến hiệu suất. Như với bất kỳ sửa đổi lược đồ nào, việc bổ sung chỉ mục phải được kiểm tra và giám sát sau khi thực hiện. Điều quan trọng là đảm bảo rằng các chỉ số bổ sung tạo ra các hiệu ứng mong muốn và không tác động tiêu cực đến hiệu suất của giải pháp. Cũng cần lưu ý rằng các chỉ mục cần có bao nhiêu không gian bổ sung cho các khóa ngoại. Điều này là cần thiết để xem xét trước khi tạo các chỉ mục và nếu chúng mang lại lợi ích thì phải được xem xét để lập kế hoạch năng lực về sau.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Dòng bộ xử lý AMD mới So sánh tốt với bộ xử lý Intel mới

  2. Cách cài đặt Libreoffice trên Ubuntu 16.04

  3. GROUP BY so với ORDER BY

  4. Kiểm tra ràng buộc trong SQL

  5. Cách không gọi các thủ tục lưu trữ được biên dịch tự nhiên của Hekaton