When Indexed Columns Fail Due to Functions or Conversions
Consider a table storing trading records:
CREATE TABLE tradelog (
id int NOT NULL,
tradeid varchar(32) DEFAULT NULL,
operator int DEFAULT NULL,
t_modified datetime DEFAULT NULL,
PRIMARY KEY (id),
KEY idx_tradeid (tradeid),
KEY idx_modified (t_modified)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
A requirement asks for the total records that occurred during July across all available years. A straightforward approach often looks like:
SELECT COUNT(*) FROM tradelog WHERE MONTH(t_modified) = 7;
Even though t_modified is indexed, the execution may be unexpectedly long. The optimizer cannot use the index for efficient tree-based lookups when a function wraps the column — in this case MONTH(). The function distorts the natural ordering of index values, so the optimizer falls back to a full index scan instead of a refined seek.
To let the optimizer exploit the index correctly, rewrite the condition using range comparisons:
SELECT COUNT(*)
FROM tradelog
WHERE (t_modified >= '2016-07-01' AND t_modified < '2016-08-01')
OR (t_modified >= '2017-07-01' AND t_modified < '2017-08-01')
OR (t_modified >= '2018-07-01' AND t_modified < '2018-08-01');
Implicit Type Conversions and Character Set Mismatches
Assume we query the same table with an integer value on a varchar column:
SELECT * FROM tradelog WHERE tradeid = 110717;
The tradeid column stores string values, but MySQL compares a number against a string. The internal rule converts the string to a numeric type:
SELECT * FROM tradelog WHERE CAST(tradeid AS signed) = 110717;
Applying CAST() effectively wraps the indexed column inside a function, again preventing tree-assisted searches and leading to a full table scan.
A similar issue arises when joining tables with mismatched character sets. For example, when a UTF-8 column is compared with a utf8mb4 column, MySQL implicitly cnoverts the lesser charset to the greater one:
SELECT * FROM trade_detail
WHERE CONVERT(tradeid USING utf8mb4) = ?;
Again, a function call on the indexed column disables efficient index usage.
Key Takeaways for Preserving Index Usage
- Avoid wrapping indexed columns in functions; restructure conditions with direct comparisons or ranges.
- Align data types — do not compare strings with integers unless you explicitly handle the conversion on the constant side.
- Keep character sets consistent across joined columns to avoid internal conversions.
- After schema or query changes, inspect execution plans with
EXPLAINto verify index usage.
Stuck Reads: Locks and Metadata Delays
A simple primary key lookup can hang for two common reasons:
SELECT * FROM t WHERE id = 1;
Issue an SHOW PROCESSLIST command. The state Waiting for table metadata lock indicates another session holds a metadata lock — perhaps an ongoing DDL or an uncommitted transaction that acquired a metadata write lock.
When using locking reads:
SELECT * FROM t WHERE id = 1 LOCK IN SHARE MODE;
If another transaction already holds an exclusive row lock on id = 1 and has not committed, the read blocks until the lock is released.
Slow Reads Even With Small Result Sets
An unindexed column query may need to scan many rows:
SELECT * FROM t WHERE c = 50000 LIMIT 1;
Without an index on c, the engine performs a sequential scan, potentially touching tens of thousands of rows.
A more surprising scenario involves a primary key lookup that reports hundreds of milliseconds in the slow log, while the same row fetched with a locking read completes instantly:
START TRANSACTION WITH CONSISTENT SNAPSHOT; -- session A
-- session B executes 1,000,000 UPDATEs on id = 1
SELECT * FROM t WHERE id = 1; -- consistent read, seems slow
SELECT * FROM t WHERE id = 1 LOCK IN SHARE MODE; -- current read, fast
After session B performs massive updates without committing, it generates extensive undo logs. A consistent read, which follows snapshot semantics, must traverse the entire undo chain to reconstruct the visible row version, whereas a locking read reads the latest committed data directly. The overhead of walking through a million undo entries accounts for the drastic difference in perceived execution time.