Cách audit, nhận diện over-index, và xây dựng chiến lược index + partition chuẩn bài

Công nghệ - 22/06/2025 00:07:46

 Đừng để "Index" trở thành gánh nặng cho database!

Trong nỗ lực tối ưu performance, nhiều dev team đã "spam index" với kỳ vọng mọi thứ sẽ nhanh hơn. Nhưng nếu không đi kèm chiến lược , cái giá phải trả là: ghi chậm, database phình to, hệ thống càng trì trệ hơn chứ không phải nhanh hơn.

Trong quá trình tối ưu hiệu năng hệ thống, nhiều đội ngũ phát triển thường mắc phải một sai lầm khá phổ biến: đánh index tràn lan với hy vọng mọi truy vấn sẽ nhanh hơn. Tuy nhiên, thực tế lại cho thấy, việc lạm dụng index mà không có chiến lược, không có partition phù hợp có thể khiến hệ thống chậm đi, tốn tài nguyên và khó bảo trì.

Bài viết này sẽ phân tích nguyên nhân, tác hại và đề xuất các phương án xử lý, cùng ưu nhược điểm của từng giải pháp.


Tác hại khi đánh Index tràn lan không Partition

  • Tăng chi phí lưu trữ và bloat database Mỗi index là một cấu trúc dữ liệu riêng biệt, chiếm thêm dung lượng đĩa. Khi số lượng index tăng lên, database sẽ phình to, kéo theo chi phí lưu trữ và backup tăng cao
  • Làm chậm các thao tác ghi (INSERT/UPDATE/DELETE) Mỗi lần dữ liệu thay đổi, tất cả các index liên quan đều phải cập nhật. Nếu có quá nhiều index, hiệu năng ghi sẽ giảm rõ rệt, đặc biệt với hệ thống write-heavy
  • Tăng chi phí bảo trì, dễ gây phân mảnh Index cần được bảo trì định kỳ (rebuild, reorganize). Nhiều index đồng nghĩa với việc bảo trì phức tạp, tốn thời gian, dễ gây downtime
  • Query Optimizer bị “rối” Khi có quá nhiều index, bộ tối ưu truy vấn có thể chọn nhầm index không tối ưu, dẫn đến truy vấn chậm hoặc không tận dụng được index
  • Lãng phí tài nguyên hệ thống Index không được sử dụng vẫn tiêu tốn RAM và CPU khi thực hiện các thao tác ghi, ảnh hưởng đến toàn bộ hệ thống

 

Vì sao Partition quan trọng?

Partition là kỹ thuật phân chia dữ liệu lớn thành các phân vùng nhỏ hơn, giúp:

  • Giảm lượng dữ liệu phải quét khi truy vấn (partition elimination).
  • Tăng tốc độ insert/update/delete trên từng phân vùng nhỏ.
  • Dễ dàng quản lý, lưu trữ, backup và archive dữ liệu cũ.

 

Khi chỉ đánh index mà không partition, index vẫn phải bao phủ toàn bộ bảng lớn, dẫn đến hiệu quả thấp. Partition kết hợp với index giúp index nhỏ gọn, truy vấn nhanh hơn và bảo trì dễ dàng hơn.

Best Practices khi quản lý Index

  • Chỉ index các cột thực sự cần thiết: Ưu tiên cột dùng trong WHERE, JOIN, ORDER BY thường xuyên.
  • Theo dõi và loại bỏ index không dùng đến: Sử dụng các công cụ kiểm tra usage.
  • Kết hợp partitioning cho bảng lớn: Giúp index nhỏ gọn, tối ưu hiệu năng truy vấn và ghi.
  • Định kỳ review và tái cấu trúc index: Đảm bảo index phù hợp với thực tế truy vấn và dữ liệu thay đổi.
  • Tránh index trùng lặp hoặc không selective: Index trên cột ít giá trị phân biệt thường không hiệu quả.

 


Dấu hiệu cho thấy bạn đang Over-Index trong Database

1. Số lượng index trên một bảng quá nhiều, nhiều index trùng lặp hoặc gần giống nhau

Nếu bạn thấy một bảng có nhiều index trên các cột riêng lẻ hoặc các tổ hợp cột gần giống nhau, đây là dấu hiệu điển hình của over-indexing. Đặc biệt, các index chỉ khác nhau về thứ tự cột hoặc có cùng chức năng nhưng tên khác nhau.

2. Hiệu năng thao tác ghi (INSERT/UPDATE/DELETE) giảm mạnh

Khi mỗi thao tác ghi đều phải cập nhật tất cả các index liên quan, hệ thống sẽ bị chậm lại rõ rệt, nhất là với các bảng write-heavy (có nhiều thao tác ghi)

3. Dung lượng database tăng nhanh bất thường

Index chiếm thêm không gian lưu trữ. Nếu bạn thấy dung lượng database phình to mà không phải do dữ liệu gốc, hãy kiểm tra lại số lượng index

4. Tăng chi phí bảo trì và thời gian bảo trì index

Việc rebuild, reorganize, update statistics cho quá nhiều index làm tăng thời gian bảo trì, ảnh hưởng đến downtime và tài nguyên hệ thống

5. Có nhiều index không được sử dụng hoặc ít được sử dụng

Sử dụng các truy vấn thống kê (như sys.dm_db_index_usage_stats trên SQL Server hoặc pg_stat_user_indexes trên PostgreSQL) để phát hiện những index có số lần truy cập thấp hoặc bằng 0. Đây là dấu hiệu rõ ràng của index “rác”

6. Execution Plan phức tạp, Query Optimizer “bối rối”

Khi có quá nhiều index, bộ tối ưu truy vấn có thể chọn nhầm index, dẫn đến truy vấn không tận dụng được index tốt nhất hoặc execution plan trở nên phức tạp, khó kiểm soát

7. Tăng số lượng các thao tác Index Scan thay vì Index Seek

Nếu execution plan cho thấy nhiều truy vấn phải thực hiện Index Scan (quét toàn bộ index) thay vì Index Seek (tìm kiếm nhanh), có thể index không phù hợp hoặc quá nhiều index khiến optimizer không chọn đúng

Cách kiểm tra & nhận biết Over-Index

  • Kiểm tra số lượng index trên từng bảng: Nếu một bảng có hơn 5-10 index, hãy xem lại mục đích từng index.
  • Thường xuyên audit usage stats: Loại bỏ các index không được sử dụng hoặc trùng lặp.
  • Theo dõi hiệu năng ghi và bảo trì: Nếu thao tác ghi hoặc bảo trì index chậm bất thường, khả năng cao là bạn đã over-index.

 

Câu lệnh kiểm tra vấn đề này ở database MS SQL Server

1. Thống kê mức độ sử dụng index

SELECT 
    OBJECT_NAME(S.[OBJECT_ID]) AS [Table_Name],
    I.[NAME] AS [Index_Name],
    USER_SEEKS,
    USER_SCANS,
    USER_LOOKUPS,
    USER_UPDATES
FROM 
    SYS.DM_DB_INDEX_USAGE_STATS AS S
INNER JOIN 
    SYS.INDEXES AS I
    ON I.[OBJECT_ID] = S.[OBJECT_ID]
    AND I.INDEX_ID = S.INDEX_ID
WHERE 
    OBJECTPROPERTY(S.[OBJECT_ID],'IsUserTable') = 1
ORDER BY 
    USER_SEEKS + USER_SCANS + USER_LOOKUPS ASC;

Index nào có USER_SEEKS, USER_SCANS, USER_LOOKUPS đều bằng 0 nhưng USER_UPDATES lớn là index không được sử dụng cho truy vấn, chỉ tốn tài nguyên ghi.

Nội dung bài viết

2. Liệt kê các index không được sử dụng

SELECT 
    OBJECT_NAME(IX.OBJECT_ID) AS Table_Name,
    IX.name AS Index_Name,
    SUM(PS.[used_page_count]) * 8 / 1024 AS IndexSizeMB,
    IXUS.user_seeks AS NumOfSeeks,
    IXUS.user_scans AS NumOfScans,
    IXUS.user_lookups AS NumOfLookups,
    IXUS.user_updates AS NumOfUpdates
FROM 
    sys.indexes IX
INNER JOIN 
    sys.dm_db_index_usage_stats IXUS ON IXUS.index_id = IX.index_id AND IXUS.OBJECT_ID = IX.OBJECT_ID
INNER JOIN 
    sys.dm_db_partition_stats PS on PS.object_id=IX.object_id
WHERE 
    OBJECTPROPERTY(IX.OBJECT_ID,'IsUserTable') = 1
    AND IXUS.user_lookups = 0
    AND IXUS.user_seeks = 0
    AND IXUS.user_scans = 0
GROUP BY 
    OBJECT_NAME(IX.OBJECT_ID),
    IX.name,
    IXUS.user_seeks ,
    IXUS.user_scans ,
    IXUS.user_lookups ,
    IXUS.user_updates 
ORDER BY 
    IndexSizeMB DESC;

Có thể lọc thêm các index không phải là primary key hoặc unique để tránh xóa nhầm index quan trọng

Lưu ý khi kiểm tra

  • Thống kê sử dụng index sẽ bị reset khi restart database hoặc thực hiện một số thao tác bảo trì, hãy đảm bảo thời gian kiểm tra đủ dài để có dữ liệu đại diện.
  • Sau khi xác định index không sử dụng, nên kiểm tra lại với đội ngũ phát triển trước khi xóa để tránh ảnh hưởng các batch job hoặc truy vấn ít khi chạy.

 

Nội dung bài viết

Bài tập về nhà:

  1. Bạn hãy kiểm tra database của bạn và tìm câu lệnh tương tự ở Postgres, MySQL và Oracle nhé.
  2. Dựa vào những ảnh chụp trên ở database thực tế, bạn hãy comment vấn đề của database này nhé.

 


Kết Luận

Index là công cụ mạnh mẽ nhưng không phải “cứ nhiều là tốt”. Đánh index tràn lan mà không partition chỉ khiến hệ thống thêm gánh nặng, hiệu năng tổng thể giảm sút. Hãy xây dựng chiến lược index dựa trên thực tế truy vấn, kết hợp partition hợp lý, audit định kỳ và loại bỏ index không cần thiết để hệ thống database luôn vận hành tối ưu, dễ mở rộng và bảo trì.

Bạn đã audit lại hệ thống index của mình gần đây chưa? Đừng để index trở thành “gánh nặng” thay vì “trợ thủ” cho hệ thống!

Chia sẻ thêm ý kiến hoặc kinh nghiệm tối ưu index của bạn ở phần bình luận nhé!

/Son Do - I share real-world lessons, team building & developer growth.

Tài liệu tham khảo

 

#Database #IndexStrategy #PostgreSQL #SQLServer #PerformanceTuning #DevOps #SystemDesign #SoftwareEngineering #dotnet #TechLeadership #wecommit100xshare #1percentbetter

Nguồn: Son Do

Công nghệ

Xem tất cả