近期連續處理兩起生產慢查詢,根因均為 數值類型列在 WHERE 條件中與字符串常量比較,觸發隱式類型轉換,導致索引失效、全表掃描。本文通過實戰案例深度解析 MySQL 類型轉換規則、EXPLAIN 現象、優化方案與預防機制。
CREATE TABLE `appstat_day_prototype_201305` (
`day_key` DATE NOT NULL DEFAULT '1900-01-01',
`appkey` VARCHAR(20) NOT NULL DEFAULT '',
`user_total` BIGINT(20) NOT NULL DEFAULT '0',
PRIMARY KEY (`appkey`,`day_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
EXPLAIN SELECT * FROM appstat_day_prototype_201305
WHERE appkey = 12345 AND day_key BETWEEN '2013-05-23' AND '2013-05-30';
+----+-------------+------------------------------+------+---------------+------+---------+------+----------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------------------------+------+---------------+------+---------+------+----------+-------------+
| 1 | SIMPLE | appstat_day_prototype_201305 | ALL | PRIMARY | NULL | NULL | NULL | 19285787 | Using where |
+----+-------------+------------------------------+------+---------------+------+---------+------+----------+-------------+
rows=1928 萬,type=ALL,全表掃描!
EXPLAIN SELECT * FROM appstat_day_prototype_201305
WHERE appkey = '12345' AND day_key BETWEEN '2013-05-23' AND '2013-05-30';
+----+-------------+------------------------------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------------------------+-------+---------------+---------+---------+------+------+-------------+
| 1 | SIMPLE | appstat_day_prototype_201305 | range | PRIMARY | PRIMARY | 65 | NULL | 1 | Using where |
+----+-------------+------------------------------+-------+---------------+---------+---------+------+------+-------------+
rows=1,type=range,命中主鍵索引!
CREATE TABLE `poll_joined_151` (
`poll_id` BIGINT(11) NOT NULL,
`uid` BIGINT(11) NOT NULL,
`item_id` VARCHAR(60) NOT NULL,
`add_time` INT(11) NOT NULL DEFAULT '0',
KEY `idx_poll_id_uid_add_time` (`poll_id`,`uid`,`add_time`),
KEY `idx_anonymous_id_addtime` (`anonymous`,`poll_id`,`add_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
EXPLAIN SELECT * FROM poll_joined_151
WHERE poll_id = '2348993' AND anonymous = 0
ORDER BY add_time DESC LIMIT 0, 3;
+----+-------------+-----------------+------+---------------+-----------------------+---------+-------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------------+------+---------------+-----------------------+---------+-------------+------+-------------+
| 1 | SIMPLE | poll_joined_151 | ref | idx_anonymous | idx_anonymous_id_addtime | 9 | const,const | 30240 | Using where |
+----+-------------+-----------------+------+---------------+-----------------------+---------+-------------+------+-------------+
現象:雖然 poll_id 是 BIGINT,傳字符串 '2348993' 仍能用到索引(type=ref),但掃描行數 3 萬較大,非最優。
核心機制:索引存儲的是列的原始二進制值。當 WHERE 條件觸發隱式轉換時:
WHERE appkey(VARCHAR) = 12345(INT)
→ 規則 6:雙方轉 DOUBLE
→ 列側轉換:索引中每個 VARCHAR 值轉浮點數再比較
→ '12345', ' 12345', '12345abc' 都轉為 12345.0 → 無法範圍定位 → 全掃
WHERE poll_id(BIGINT) = '2348993'(VARCHAR)
→ 規則 6:雙方轉 DOUBLE
→ 常量側轉換:'2348993' 確定性轉為 2348993.0
→ 索引可正常範圍查找 → ref 訪問
→ 但存在額外 CPU 開銷,且極大數值可能精度丟失(DOUBLE 53 位尾數)
SELECT CAST('18446744073709551615' AS DOUBLE) = 18446744073709551615; -- 1 (精度丟失,實際不等)
SELECT CAST('18446744073709551615' AS DECIMAL) = 18446744073709551615; -- 0 (精確)
超過 2^53 (≈9e15) 的整數轉 DOUBLE 會丟失精度,導致錯誤匹配。
ALTER TABLE t ADD INDEX idx_cast_appkey ((CAST(appkey AS UNSIGNED))); 兜底兼容歷史錯誤查詢。long_query_time=1、log_queries_not_using_indexes=ON,定期分析 type=ALL 或 rows > 10000 的查詢。SELECT * FROM information_schema.PROCESSLIST
WHERE INFO LIKE '%WHERE%' AND INFO REGEXP '[0-9]+\s*[=<>]\s*[''"]';
隱式類型轉換是 MySQL 索引失效的頭號隱形殺手。VARCHAR 列 = 數值 導致列側轉換、全表掃描,災難性後果;BIGINT 列 = '字符串' 雖能用索引但有精度風險和 CPU 額外開銷。鐵律:常量類型與列類型嚴格對齊。建立代碼規範、Code Review 機制、慢查詢審計體系,從源頭杜絕此類低級錯誤。