Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Advanced MySQL Indexing and Query Optimization Techniques

Tech Sep 22 1

Checking Query Execution Cost

To evaluate the efficiency of a query execution plan, inspect the optimizer's cost calculation.

SHOW SESSION STATUS LIKE 'Last_query_cost';

Prefix Indexing for String Columns

Indexing long text columns entirely can consume excessive space. Instead, index a prefix of the string that offers high selectivity. For instance, on a product_catalog table, we can calculate the selectivity of the full product_sku versus its prefixes.

-- Calculate selectivity of the full column
SELECT COUNT(DISTINCT product_sku) / COUNT(*) AS selectivity_ratio
FROM product_catalog;

-- Calculate selectivity of the first 7 characters
SELECT COUNT(DISTINCT LEFT(product_sku, 7)) / COUNT(*) AS prefix_selectivity_ratio
FROM product_catalog;

If the prefix selectivity is close to the full column selectivity, a prefix index is sufficient.

CREATE INDEX idx_sku_prefix ON product_catalog (product_sku(7));

InnoDB Clustered Indexes

InnoDB uses clustered indexes where the primary key determines the physical order of data. The leaf nodes of the primary index contain the entire data row. Secondary indexes store the primary key value as a pointer to the row. Consequently, keeping the primary key compact is critical to avoiding bloat in secondary indexes.

Compressed (Prefix Compressed) Indexes

Some storage engines compress index blocks to save space. For example, if sequential entries are 'optimization' and 'optimizer', the second entry might be stored as '11,er', representing the shared prefix length and the suffix.

Avoiding Redundant Indexes

Creating duplicate indexes wastes memory and write resources. For example, if a composite index exists on (department_id, status), a separate index on (department_id) is redundant because the composite index can already satisfy queries filtering on just department_id.

CREATE TABLE employee_records (
    id INT PRIMARY KEY,
    department_id INT,
    status VARCHAR(20),
    INDEX idx_dept_status (department_id, status),
    INDEX idx_dept (department_id) -- This is redundant
);

Optimizing Low-Cardinality Columns

For columns with a limited set of values (low cardinality), using an IN list allows the optimizer to efficiently use range scans or index lookups.

SELECT * FROM user_accounts 
WHERE account_status IN ('active', 'pending') 
AND last_login > '2023-01-01';

Pagination Optimization

Deep pagination with large offsets (e.g., LIMIT 10000, 10) is inefficient because the database must scan and discard the first 10,000 rows. A more efficient approach is a "deferred join" or seeking specific positions.

-- Inefficient: Scans 10,010 rows
SELECT * FROM transaction_log ORDER BY created_at LIMIT 10000, 10;

-- Efficient: Uses covering index to find IDs, then joins
SELECT t.* 
FROM transaction_log t
INNER JOIN (
    SELECT id 
    FROM transaction_log 
    ORDER BY created_at 
    LIMIT 10000, 10
) AS tmp USING (id);

Batching Large Operations

Large delete or update operations can lock tables for extended periods. Breaking them into smaller batches reduces contention.

-- Instead of deleting all at once
-- DELETE FROM audit_logs WHERE created_date < NOW() - INTERVAL 3 MONTH;

-- Use a batched approach
SET @rows_affected = 1;
WHILE @rows_affected > 0 DO
  DELETE FROM audit_logs 
  WHERE created_date < NOW() - INTERVAL 3 MONTH 
  LIMIT 5000;
  SET @rows_affected = ROW_COUNT();
END WHILE;

Optimizing MIN() Queries

When fetching the minimum value from an indexed column, using LIMIT 1 with an ORDER BY clause can sometimes be as efficient as the MIN() aggregate function.

-- Standard approach
SELECT MIN(id) FROM items;

-- Alternative using index ordering
SELECT id FROM items ORDER BY id ASC LIMIT 1;

Conditional Counting

To count occurrences of multiple specific values in a single pass, use conditional aggregation rather than multiple queries.

SELECT 
    SUM(CASE WHEN role = 'admin' THEN 1 ELSE 0 END) AS admin_count,
    SUM(CASE WHEN role = 'editor' THEN 1 ELSE 0 END) AS editor_count
FROM users;

Join and Group By Optimization

Ensure columns used in JOIN conditions are indexed. When using GROUP BY, referencing columns from a single table allows the optimizer to utilize indexes more effectively.

Subquery Grouping

Moving GROUP BY operations into a subquery before joining can significantly reduce the cost by processing fewer rows.

-- Higher cost join
SELECT u.id, u.name, o.total
FROM users u
INNER JOIN orders o ON u.id = o.user_id
GROUP BY u.id;

-- Lower cost using subquery
SELECT u.id, u.name, summary.total
FROM users u
INNER JOIN (
    SELECT user_id, SUM(amount) as total
    FROM orders
    GROUP BY user_id
) AS summary ON u.id = summary.user_id;

Implicit Group By Sorting

By default, MySQL sorts results when using GROUP BY. If sorting is not required, explicitly disable it to save resources.

-- Implicit sort performed
SELECT department, COUNT(*) FROM staff GROUP BY department;

-- Sort explicitly disabled
SELECT department, COUNT(*) FROM staff GROUP BY department ORDER BY NULL;

Covering Indexes

A covering index includes all columns required by a query (SELECT, JOIN, WHERE). This allows the storage engine to satisfy the query solely from the index tree without accessing the data rows (table lookup).

Covering Indexes for Pagination

Applying the covering index technique to pagination drastically improves performance by preventing the engine from reading full rows for offset entries.

-- This query benefits if there is an index on (salary, id)
SELECT id, employee_name 
FROM compensation 
INNER JOIN (
    SELECT id 
    FROM compensation 
    ORDER BY salary 
    LIMIT 10000, 10
) AS tmp USING (id);

Query Profiling

To analyze the execution time of queries, enable profiling. Note that this feature is deprecated in newer versions in favor of the Performance Schema, but remains useful for older environments.

SET profiling = 1;

SELECT * FROM large_table WHERE random_col > 50;

SHOW PROFILES;

SET profiling = 0;

Query Cache Limitations

Queries containing non-deterministic functions like NOW(), CURRENT_DATE(), or RAND() cannot be cached, as their results change with every execution.

Full-Text Search Indexes

Full-text indexes utilize an inverted index structure. A dictionary of unique words is maintained, mapping each word to the list of document IDs containing it. This allows for rapid retrieval of records containing specific terms.

System Scalability

Scalability refers to the system's ability to handle increasing loads while maintaining performance. Architectural strategies for scalability include:

  • Master-Master Replication: Active-Active topology for read/write distribution.
  • Master-Slave Replication: Read scaling by offloading queries to replicas.
  • Sharding: Horizontal partitioning of data across multiple servers.
  • Shared Storage Clusters: Using SAN or NAS for distributed data access.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.