Indexes & Query Optimization

লেসন ২৮ / ৫০

Index কী?

Database-এর "table of contents" — column-এর উপর তৈরি sorted lookup structure। SELECT দ্রুত করে কিন্তু INSERT/UPDATE ধীর করে।

Index Types

-- Clustered (table-এ একটি, data physically sorted)
CREATE CLUSTERED INDEX IX_Products_Id ON Products(Id);
-- PK by default এটি তৈরি করে

-- Non-clustered (একাধিক, separate B-tree)
CREATE INDEX IX_Products_Name ON Products(Name);

-- Composite (একাধিক column)
CREATE INDEX IX_Products_Cat_Price ON Products(CategoryId, Price);
-- Order matters! WHERE CategoryId = 5 — ব্যবহার হবে
-- WHERE Price = 100 — ব্যবহার হবে না

-- Unique
CREATE UNIQUE INDEX UX_Users_Email ON Users(Email);

-- Covering (INCLUDE additional columns)
CREATE INDEX IX_Products_Cat
    ON Products(CategoryId)
    INCLUDE (Name, Price);
-- "covering" — query শুধু index থেকে answer করতে পারে

-- Filtered (subset)
CREATE INDEX IX_Active_Users ON Users(LastLogin)
    WHERE Status = 'active';

-- Drop
DROP INDEX IX_Products_Name ON Products;

কোথায় Index দেবেন

  • WHERE clause-এ যে column-এ filter
  • JOIN-এর ON column
  • ORDER BY / GROUP BY
  • FK column

কোথায় দেবেন না

  • খুব ছোট table
  • খুব বেশি INSERT/UPDATE
  • Low cardinality (gender, status — মাত্র কয়েকটি value)

Execution Plan দেখা

SET STATISTICS IO ON;
SET STATISTICS TIME ON;

-- Estimated plan: Ctrl+L (SSMS)
-- Actual plan: Ctrl+M then run

SELECT * FROM Products WHERE Name = 'Pen';

-- দেখুন:
-- Index Seek (good ⭐)
-- Index Scan (medium)
-- Table/Clustered Index Scan (slow ❌)

Slow Query চিনুন

-- DMV — সবচেয়ে দামি queries
SELECT TOP 20
    qs.execution_count,
    qs.total_logical_reads,
    qs.total_elapsed_time / 1000 AS total_ms,
    SUBSTRING(qt.text, qs.statement_start_offset/2,
        (CASE qs.statement_end_offset
            WHEN -1 THEN DATALENGTH(qt.text)
            ELSE qs.statement_end_offset END
            - qs.statement_start_offset)/2) AS query
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
ORDER BY total_logical_reads DESC;

-- Missing index hint
SELECT * FROM sys.dm_db_missing_index_details;

Common Mistakes ⚠️

-- ❌ SELECT *
SELECT * FROM Products;

-- ✅ Specific columns
SELECT Id, Name, Price FROM Products;

-- ❌ Function on indexed column (index ব্যবহার হবে না)
WHERE YEAR(CreatedAt) = 2024
WHERE UPPER(Name) = 'PEN'

-- ✅
WHERE CreatedAt >= '2024-01-01' AND CreatedAt < '2025-01-01'

-- ❌ Leading wildcard
WHERE Name LIKE '%phone'

-- ✅
WHERE Name LIKE 'phone%'

-- ❌ N+1 in EF (loop-এ query)
foreach (var c in categories)
    var products = _db.Products.Where(p => p.CategoryId == c.Id).ToList();

-- ✅ Include
var data = _db.Categories.Include(c => c.Products).ToList();

Index Maintenance

-- Fragmentation
SELECT * FROM sys.dm_db_index_physical_stats(...);

-- Rebuild (heavy, locks)
ALTER INDEX IX_Products_Name ON Products REBUILD;

-- Reorganize (online, lighter)
ALTER INDEX IX_Products_Name ON Products REORGANIZE;

-- Update statistics
UPDATE STATISTICS Products;