- vừa được xem lúc

Tất tần tật về Index trong SQL Server

0 0 10

Người đăng: Thuận Nguyễn

Theo Viblo Asia

Trong MS SQL Server, Index là một cấu trúc dữ liệu lưu dưới dạng table small dùng để cải thiện tốc độ các câu query có điều kiện(where, join, group by, order by) và tìm kiếm dữ liệu trong table hoặc view. Index hoạt động tương tự như mục lục của một cuốn sách, giúp nhanh chóng xác định vị trí row trong table mà không cần quét toàn bộ table.

Kiến thức cần biết

Có 2 loại index trong SQL Server:

Clustered Index

- Sắp xếp data vật lý theo 1 thứ tự nhất định

- 1 bảng chỉ có 1 index clustered, mặc định Sql Server sẽ tạo 1 index dạng clustered index trên primary key(unique, not null).

- 1 index clustered có thể đánh gom nhiều cột(composite clustered index)

Non-Clustered Index

- Không ảnh hưởng tới thứ tự sắp xếp của table

- 1 bảng có nhiều index none-clustered lưu trữ con trỏ trỏ tới vị trí của index

- Có thể đánh nhiều cột thành 1 index(composite non-clustered index)

Systax:

-- get list index of table
sp_helpindex 'table_name' -- create index
create index index_name
on table_name(column1, column2, .., columnN); -- create single index
create index products_category
on products(category); -- create composite index
create index products_category_brand
on products(category, brand_id); -- create unique index
create unique index products_category_brand
on products(category, brand_id); -- create filter index
create index products_brand
on products(brand_id)
where brand_id = 1; -- create unique and filter index
create unique index products_brand_1_category
on products (category, brand_id)
where brand_id = 1; -- create include column index
create nonclustered index products_brand on products (brand_id) include (category) -- create columnstore index: chuyên sử dụng trong phân tích và báo cáo
create nonclustered columnstore index nclstore_quantity_price
on sales(auantity, price); -- create xml index: chuyên sử dụng cho column lưu trữ data dạng xml
path: tìm kiếm theo path
create xml index pidx_productinfo_path
on products (productinfo)
using xml index pidx_productinfo
for path; value: tìm kiếm theo value
create xml index pidx_productinfo_value
on products (productinfo)
using xml index pidx_productinfo
for value; property: tìm kiếm theo property
create xml index pidx_productinfo_property
on products (productinfo)
using xml index pidx_productinfo
for property; VD truy vấn sử dụng đường dẫn (path)
select productinfo.query('/product/name')
from products
where productinfo.exist('/product[category="electronics"]') = 1; VD truy vấn tìm kiếm giá trị
select productinfo.value('(/product/price)[1]', 'decimal(10, 2)')
from products
where productinfo.exist('/product[name="laptop"]') = 1; VD truy vấn truy xuất thuộc tính
select productinfo.value('(/product/@id)[1]', 'int')
from products; -- update index
alter index index_name on table_name rebuild/recognize/disable. -- delete index
drop index index_name on table_name 

Index trong tối ưu performence

  1. Cân nhắc khi đánh index:
  • Index Clustered: Thường được sử dụng cho các column mà các truy vấn thường xuyên order hoặc group by theo thứ tự. Một bảng chỉ có thể có một index clustered, thường là private key
  • Index Non-Clustered: Thường được sử dụng cho các cột được tìm kiếm hoặc lọc thường xuyên, nhưng không nhất thiết phải sắp xếp theo thứ tự.
  • Index Composite: Các chỉ mục bao gồm nhiều cột có thể rất hữu ích cho các truy vấn phức tạp sử dụng nhiều cột trong các điều kiện tìm kiếm và sắp xếp. Thứ tự của các cột trong chỉ mục rất quan trọng và nên phản ánh thứ tự của các điều kiện trong truy vấn.

Ví dụ:

Table có INDEX (A, B), thì truy vấn WHERE A = ? AND B = ?, excution plan sẽ sử dụng index này

Table có INDEX (A, B) và truy vấn WHERE B = ?, excution plan có thể không sử dụng index này

  1. Khi column có nhiều giá trị null cần giảm số lượng row cần quét khi truy vấn, tạo một Filtered Index chỉ bao gồm các hàng mà cột không có giá trị NULL.
create nonclustered index idx_filtered_non_nulls on table_name (column_name) where column_name is not null; 
  1. Nếu bạn cần bao gồm cả các giá trị NULL và non-NULL trong chỉ mục, bạn có thể sử dụng một cột tính toán để chuyển đổi các giá trị NULL thành một giá trị khác (ví dụ: 'UNKNOWN' hoặc một giá trị mặc định) và sau đó đánh index trên cột tính toán đó.
alter table table_name
add computed_column as isnull(column_name, 'default_value'); create nonclustered index idx_computed_column
on table_name (computed_column); 
  1. Lưu ý với các câu query Where Non-Sargable và Sargable
1. Các Điều Kiện Non-Sargable: không thể dùng index dẫn đến table scan
<> hoặc !=
!> hoặc !<
not exists
not in
not like
like '%<literal>'
or
tính toán trên cột (function(column)) 2. Các Điều Kiện Sargable: có thể tận dụng index tìm kiếm
=
>
<
>=
<=
between
in
like 'abc%' 
  1. Sử dụng Include Covering Index

Để loại bỏ KeyLookup trong excution plan khi cần lấy thêm thông tin(ví dụ: select id, name from products where id = 1) thì ta có thể include name vào non-clustered index

Trường hợp 1: KeyLookup biến mất tuy nhiên thêm cột vào Index tăng chi phí duy trì và phức tạp hơn
create nonclustered index idx_id_name on products(id, name) Trường hợp 2: sử dụng covering index, tuy nhiên cần cân nhắc chi phí khi include column theo đôi khi còn quá chi phí so với keylookup nên cần phải xem thay đổi thực tế trên excution plan
create nonclustered index idx_id on products(id) include (name) 
  1. Gather statistic cho index
Sử dụng AUTO_UPDATE_STATISTICS: Tính năng này được bật mặc định và nên được giữ nguyên để đảm bảo statistic luôn cập nhật
alter database [databasename] set auto_update_statistics on; Sử dụng AUTO_UPDATE_STATISTICS_ASYNC: cập nhật statistic bất đồng bộ, giúp tránh ảnh hưởng đến hiệu suất của các truy vấn, Sql Server chạy background update
alter database [databasename] set auto_update_statistics_async on; Sử dụng lệnh UPDATE STATISTICS: cập nhập statistic cho table
update statistics table_name; Cập nhật statistic cho index
update statistics table_name index_name; Sử dụng lệnh sp_updatestats: update tất cả statistic trong database
exec sp_updatestats; 

Đánh index tốt nhất cho các column có loại dữ liệu dễ dàng sắp xếp, có kích thước nhỏ như số, ngày giờ, với những column dạng nvarchar, varchar, text cần dùng Full Text Search Index giúp tăng tốc độ truy vấn tìm kiếm văn bản dài và phức tạp (tham khảo bài viết về đánh Fulltext Search: https://www.linkedin.com/pulse/full-text-search-sql-server-practical-guide-baligh-mehrez/)

-- Tạo Catalog Fulltext
create fulltext catalog myfulltextcatalog; -- Tạo Index fulltext
create fulltext index on table_name(column_name)
key index idx_primary_key
on myfulltextcatalog; 
  1. Xử lý Redundant, Overlapping & Duplicate Index(Trùng lặp Index, dẫn tới ghi đè và thừa index) (code này là nguồn copy nha)
WITH indexcolumns AS (SELECT I.object_id AS TableObjectId, Object_schema_name(I.object_id) + '.' + Object_name(I.object_id) AS TableName, I.index_id AS IndexId, I.NAME AS IndexName, ( IndexUsage.user_seeks + IndexUsage.user_scans + IndexUsage.user_lookups ) AS IndexUsage, IndexUsage.user_updates AS IndexUpdates, (SELECT CASE is_included_column WHEN 1 THEN NULL ELSE column_id END AS [data()] FROM sys.index_columns AS IndexColumns WHERE IndexColumns.object_id = I.object_id AND IndexColumns.index_id = I.index_id ORDER BY index_column_id, column_id FOR xml path('')) AS ConcIndexColumnNrs, (SELECT CASE is_included_column WHEN 1 THEN NULL ELSE Col_name(I.object_id, column_id) END AS [data()] FROM sys.index_columns AS IndexColumns WHERE IndexColumns.object_id = I.object_id AND IndexColumns.index_id = I.index_id ORDER BY index_column_id, column_id FOR xml path('')) AS ConcIndexColumnNames, (SELECT CASE is_included_column WHEN 1 THEN column_id ELSE NULL END AS [data()] FROM sys.index_columns AS IndexColumns WHERE IndexColumns.object_id = I.object_id AND IndexColumns.index_id = I.index_id ORDER BY column_id FOR xml path('')) AS ConcIncludeColumnNrs, (SELECT CASE is_included_column WHEN 1 THEN Col_name(I.object_id, column_id) ELSE NULL END AS [data()] FROM sys.index_columns AS IndexColumns WHERE IndexColumns.object_id = I.object_id AND IndexColumns.index_id = I.index_id ORDER BY column_id FOR xml path('')) AS ConcIncludeColumnNames FROM sys.indexes AS I LEFT OUTER JOIN sys.dm_db_index_usage_stats AS IndexUsage ON IndexUsage.object_id = I.object_id AND IndexUsage.index_id = I.index_id AND IndexUsage.database_id = Db_id())
SELECT C1.tablename AS 'TableName', C1.indexname AS 'Index1', C2.indexname AS 'Index2', CASE WHEN ( C1.concindexcolumnnrs = C2.concindexcolumnnrs ) AND ( C1.concincludecolumnnrs = C2.concincludecolumnnrs ) THEN 'Exact duplicate' WHEN ( C1.concindexcolumnnrs = C2.concindexcolumnnrs ) THEN 'Different includes' ELSE 'Overlapping columns' END -- , C1.ConcIndexColumnNrs -- , C2.ConcIndexColumnNrs , C1.concindexcolumnnames AS ConcIncludeColumnNrs, C2.concindexcolumnnames AS ConcIncludeColumnNrs -- , C1.ConcIncludeColumnNrs -- , C2.ConcIncludeColumnNrs , C1.concincludecolumnnames, C2.concincludecolumnnames, C1.indexusage, C2.indexusage, C1.indexupdates, C2.indexupdates, 'DROP INDEX ' + C2.indexname + ' ON ' + C2.tablename AS Drop2, 'DROP INDEX ' + C1.indexname + ' ON ' + C1.tablename AS Drop1
FROM indexcolumns AS C1 INNER JOIN indexcolumns AS C2 ON ( C1.tableobjectid = C2.tableobjectid ) AND ( -- exact: show lower IndexId as 1 ( C1.indexid < C2.indexid AND C1.concindexcolumnnrs = C2.concindexcolumnnrs AND C1.concincludecolumnnrs = C2.concincludecolumnnrs ) -- different includes: show longer include as 1 OR ( C1.concindexcolumnnrs = C2.concindexcolumnnrs AND Len(C1.concincludecolumnnrs) > Len(C2.concincludecolumnnrs) ) -- overlapping: show longer index as 1 OR ( C1.indexid <> C2.indexid AND C1.concindexcolumnnrs <> C2.concindexcolumnnrs AND C1.concindexcolumnnrs LIKE C2.concindexcolumnnrs + ' %' ) )
ORDER BY C1.tablename, C1.concindexcolumnnrs 
  1. Xử lý các Heap Table: là các table không có Clustered Index, table không được sắp xếp ưu điểm là INSERT nhanh nhưng nhược điểm là query chậm, trường hợp SQL Server phải scan trong heap table sẽ tăng cost cho quá trình scan (so với bảng có Clustered Index), ảnh hưởng tiêu cực đến hiệu năng hệ thống. Code kiểm tra table chưa có Clustered Index
SELECT t.name AS TableName, i.type_desc AS IndexType
FROM sys.tables t LEFT JOIN sys.indexes i ON t.object_id = i.object_id AND i.index_id = 1
WHERE i.object_id IS NULL; 

Thống kê tình trạng sử dụng của index

SELECT DB_NAME(s.database_id) AS DatabaseName, OBJECT_NAME(s.object_id) AS TableName, i.name AS IndexName, i.type_desc AS IndexType, i.is_primary_key AS IsPrimaryKey, i.is_unique AS IsUnique, s.user_seeks AS UserSeeks, s.user_scans AS UserScans, s.user_lookups AS UserLookups, s.user_updates AS UserUpdates, s.last_user_seek AS LastUserSeek, s.last_user_scan AS LastUserScan, s.last_user_lookup AS LastUserLookup, s.last_user_update AS LastUserUpdate
FROM sys.dm_db_index_usage_stats AS s
JOIN sys.indexes AS i ON s.object_id = i.object_id AND s.index_id = i.index_id
WHERE s.database_id = DB_ID()
ORDER BY (s.user_seeks + s.user_scans + s.user_lookups) DESC; user_seeks: tần suất chỉ mục được sử dụng để tìm kiếm dữ liệu.
user_scans: tần suất chỉ mục được sử dụng để quét dữ liệu.
user_lookups: tần suất chỉ mục được sử dụng để tra cứu dữ liệu từ bảng.
user_updates: tần suất chỉ mục phải được cập nhật do các thay đổi trong dữ liệu. 
  1. Xác địng index thiếu bằng excution plan hoặc dùng tool của Sql Server(SQL Server's Database Engine Tuning Advisor) hoặc code, sau đó phân tích điểm lợi điểm hại trước khi tạo index theo recommend
SELECT migs.avg_total_user_cost * (migs.avg_user_impact / 100.0) * (migs.user_seeks + migs.user_scans) AS improvement_measure, OBJECT_NAME(mid.[object_id]) AS TableName, 'CREATE INDEX [IX_' + OBJECT_NAME(mid.[object_id]) + '_' + REPLACE(REPLACE(REPLACE(ISNULL(mid.equality_columns, ''), ', ', '_'), '[', ''), ']', '') + CASE WHEN mid.[equality_columns] IS NOT NULL AND mid.[inequality_columns] IS NOT NULL THEN '_' ELSE '' END + REPLACE(REPLACE(REPLACE(ISNULL(mid.inequality_columns, ''), ', ', '_'), '[', ''), ']', '') + ']' + ' ON ' + mid.statement + ' (' + ISNULL (mid.equality_columns,'') + CASE WHEN mid.equality_columns IS NOT NULL AND mid.inequality_columns IS NOT NULL THEN ',' ELSE '' END + ISNULL (mid.inequality_columns, '') + ')' + ISNULL (' INCLUDE (' + mid.included_columns + ')', '') AS create_index_statement, migs.*, mid.database_id, mid.[object_id]
FROM sys.dm_db_missing_index_groups mig
INNER JOIN sys.dm_db_missing_index_group_stats migs ON migs.group_handle = mig.index_group_handle
INNER JOIN sys.dm_db_missing_index_details mid ON mig.index_handle = mid.index_handle
WHERE mid.database_id = DB_ID() -- Replace with your database ID
ORDER BY migs.avg_total_user_cost * migs.avg_user_impact * (migs.user_seeks + migs.user_scans) DESC; 
  1. Xử lý fragment index cũng là vấn đề gây chậm query
-- kiểm tra mức độ phân mảnh của index
SELECT DB_NAME(ps.database_id) AS DatabaseName, OBJECT_NAME(ps.object_id) AS TableName, i.name AS IndexName, ps.index_id, ps.avg_fragmentation_in_percent, --phần trăm phân mảnh trung bình của index. ps.page_count
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'SAMPLED') AS ps
JOIN sys.indexes AS i ON ps.object_id = i.object_id AND ps.index_id = i.index_id
WHERE ps.database_id = DB_ID() AND ps.page_count > 1000 -- Chỉ xem các chỉ mục với hơn 1000 trang để tránh kiểm tra các chỉ mục nhỏ
ORDER BY ps.avg_fragmentation_in_percent DESC; mức độ phân mảnh:
- dưới 5%: chấp nhận được, không cần hành động.
- 5% - 30%: recognize ngay alter index index_name on table_name recognize;
- trên 30%: rebuild ngay alter index index_name on table_name rebuild; 

Kiểm tra index không sử dụng

SELECT OBJECT_NAME(i.object_id) AS TableName, i.name AS IndexName, i.index_id, i.type_desc AS IndexType, i.is_primary_key, i.is_unique, i.data_space_id, i.ignore_dup_key, i.is_disabled, i.is_hypothetical, i.allow_row_locks, i.allow_page_locks
FROM sys.indexes AS i
LEFT JOIN sys.dm_db_index_usage_stats AS s ON i.object_id = s.object_id AND i.index_id = s.index_id
WHERE i.type_desc <> 'HEAP' AND s.index_id IS NULL AND i.object_id > 100; -- Loại bỏ các đối tượng hệ thống 
  1. SQL standard reports: sử dụng report để check status index đang sử dụng

Bình luận

Bài viết tương tự

- vừa được xem lúc

Giới thiệu Stored Procedure trong SQL Server

Stored Procedure là 1 phần không thể thiếu của SQL Server. Chúng có thể hỗ trợ rất nhiều cho lập trình và cấu hình cơ sở dữ liệu.

0 0 164

- vừa được xem lúc

sử dụng index trong sql query

Index là một trong những yếu tố quan trọng nhất góp phần vào việc nâng cao hiệu suất của cơ sở dữ liệu. Index trong SQL tăng tốc độ của quá trình truy vấn dữ liệu bằng cách cung cấp phương pháp truy xuất nhanh chóng tới các dòng trong các bảng, tương tự như cách mà mục lục của một cuốn sách giúp bạn

0 0 195

- vừa được xem lúc

Hướng dẫn sửa lỗi không cài được SQL Server

Hôm qua do yêu cầu môn học, mình có cài lại Microsoft SQL Server. Trước đó mình có cài rồi, nhưng rồi lâu không dùng nên gỡ ra cho nhẹ máy. Bây giờ có dịp cần nên mình mới cài lại. Chi tiết lỗi mình gặp phải.

0 0 134

- vừa được xem lúc

Bạn nên tránh sử dụng Soft Delete khi có thể, và đây là lý do tại sao

Con người luôn luôn mắc sai lầm. Vì vậy, việc "lo xa" trước mọi tình huống xấu nhất chưa bao giờ là thừa.

0 0 144

- vừa được xem lúc

Sử dụng trigger trong SQL qua ví dụ cơ bản.

Trigger là gì . Cú pháp của Trigger. CREATE TRIGGER tên_trigger ON tên_bảng. FOR {DELETE, INSERT, UPDATE}.

0 0 161

- vừa được xem lúc

Khác biệt giữa khóa chính và khóa ngoại trong SQL

Các khoá chính và khóa ngoại là hai loại ràng buộc có thể được sử dụng để thực thi toàn vẹn dữ liệu trong các bảng SQL Server và đây là những đối tượng cơ sở dữ liệu quan trọng. Trong bài này, tôi muố

0 0 149