当我针对整数查询 varchar 列(已索引)时,它运行得非常慢。我认为 mysql 能够推断出这一点并将参数转换为字符串,但是当我使用整数进行过滤时,它避免了索引。
这样可以吗?是整理问题吗?我是否应该始终手动将整数转换为字符串以使 varchar 索引正常工作?
运行mysql 5.7
varchar列是一个外部id,我们不控制它是整数还是字母数字。有时用户想要通过我们的内部整数 id 查找对象,有时通过他们的 id 查找,因此我们使用: WHERE id = ?或外部_id = ?
在所有其他情况下,参数将作为浮点进行比较 (双精度)数字。例如,字符串和的比较 数字操作数作为浮点数的比较进行 数字。
由于您要将字符串列与整数常量进行比较,MySQL 必须将列中的每个值转换为浮点数进行比较,并且可能不会使用索引(如果相反,即整数列与字符串常量)。
但更重要的是,这样的比较会产生意想不到的结果:
select '123abc' = 123 -- true
话虽如此,改变这一点并不难:
select '123abc' = 123 -- true
对此:
select '123abc' = '123' -- false
将列(VARCHAR 或 INT)与值(字符串或数字)进行比较有 4 种组合。 其中一个案例效率低下,因为没有索引是有用的:
WHERE varchar_col = 123
因为它不能使用索引,会将每个字符串转换为数字来进行测试。
其他情况可以在列上使用索引。 (由于其他原因,这样的索引可能或可能不实际使用。)
这不慢:
WHERE int_col = "123"
因为
"123"
在解析时会转换为简单的数字。
当然,这些工作“符合预期”。即使用字符串比较或数字比较。
WHERE varchar_col = "123"
WHERE int_col = 123
(
FLOAT
是另一个话题了。)
问题不在于数据类型或排序规则,而在于您使用
OR
来搜索两个不同的列。
考虑一下这个类比:假设我要求你在电话簿中查找名为 Gammel 的人。你问:“这是姓还是名?”我回答:“请找到所有案例,无论是名字还是姓氏。”
现在你有问题了。
SELECT ... FROM telephone_book
WHERE last_name = 'Gammel' OR first_name = 'Gammel';
这本书是按姓氏排序的,因此应该很快就能找到与姓氏匹配的条目。但我也要求提供所有与名字匹配的内容。对于任何姓氏的人来说,这些内容可能会在整本书中随机分布。您现在必须艰难地搜索这本书,一次一页。
解决
OR
优化问题的常见解决方案是将 UNION
与两个在各自索引上搜索的单独查询结合使用。
SELECT ... FROM telephone_book
WHERE last_name = 'Gammel'
UNION
SELECT ... FROM telephone_book
WHERE first_name = 'Gammel';
假设
first_name
上有不同的索引,该联合中的后一个查询将使用它以优化的方式查找按名字匹配的条目。我们已经知道它可以对姓氏做到这一点。
然后,一旦找到与任一条件匹配的行子集(希望很小),这些集合就会合并到一个结果中。
根据https://dev.mysql.com/doc/refman/8.0/en/mysql-indexes.html
如果无法在不进行转换的情况下直接比较值,则不同列的比较(例如,将字符串列与时间列或数字列进行比较)可能会阻止使用索引。对于数字列中的给定值(例如 1),它可能与字符串列中的任意数量的值(例如“1”、“1”、“00001”或“01.e1”)进行比较。这排除了对字符串列使用任何索引。
对于这样的表:
create table test_user
(
id int auto_increment primary key,
name varchar(45) default '' null
);
create index idx_name on test_user (name);
mysql 如果列与字符串进行比较,将使用索引:
mysql> explain select * from coloro.test_user where name = 'fdsa';
+--+-----------+---------+----------+----+-------------+--------+-------+-----+----+--------+-----------+
|id|select_type|table |partitions|type|possible_keys|key |key_len|ref |rows|filtered|Extra |
+--+-----------+---------+----------+----+-------------+--------+-------+-----+----+--------+-----------+
|1 |SIMPLE |test_user|null |ref |idx_name |idx_name|183 |const|1 |100 |Using index|
+--+-----------+---------+----------+----+-------------+--------+-------+-----+----+--------+-----------+
如果列与整数比较,mysql将不会使用索引:
mysql> explain select * from coloro.test_user where name = 1;
+--+-----------+---------+----------+-----+-------------+--------+-------+----+----+--------+------------------------+
|id|select_type|table |partitions|type |possible_keys|key |key_len|ref |rows|filtered|Extra |
+--+-----------+---------+----------+-----+-------------+--------+-------+----+----+--------+------------------------+
|1 |SIMPLE |test_user|null |index|idx_name |idx_name|183 |null|1 |100 |Using where; Using index|
+--+-----------+---------+----------+-----+-------------+--------+-------+----+----+--------+------------------------+
另一方面,如果列类型是int,那么mysql将使用索引。
explain select * from coloro.test_user where id = '1';
+--+-----------+---------+----------+-----+-------------+-------+-------+-----+----+--------+-----+
|id|select_type|table |partitions|type |possible_keys|key |key_len|ref |rows|filtered|Extra|
+--+-----------+---------+----------+-----+-------------+-------+-------+-----+----+--------+-----+
|1 |SIMPLE |test_user|null |const|PRIMARY |PRIMARY|4 |const|1 |100 |null |
+--+-----------+---------+----------+-----+-------------+-------+-------+-----+----+--------+-----+