2016-11-28 73 views
0

背景:服务器应用程序(C#)使用下面的查询(简化)从数据库中提取订单数据。我记录了这需要的平均时间,现在执行时间突然增加(平均从5ms到50ms)。所以我开始检查这个查询。使用变量时不使用索引

String ordernr = "123456789"; 
String sql = "SELECT * FROM MYDB.`MYTABLE` WHERE id = @id"; 
using (MySqlCommand cmd = new MySqlCommand(sql, conn)) 
{ 
    cmd.Parameters.Add("@id", MySqlDbType.VarChar, 16).Value = ordernr; 
    using (MySqlDataReader reader = cmd.ExecuteReader()) 
    { 
     //reading data 
    } 
} 

当我检查在MySQL查询时,我得到如下结果:

MariaDB [MYDB]> SET @id = '123456789'; 
Query OK, 0 rows affected (0.00 sec) 

MariaDB [MYDB]> explain SELECT SQL_NO_CACHE * FROM MYDB.`MYTABLE` WHERE id = @id ; 
+------+-------------+-----------+------+---------------+------+---------+------+---------+-------------+ 
| id | select_type | table  | type | possible_keys | key | key_len | ref | rows | Extra  | 
+------+-------------+-----------+------+---------------+------+---------+------+---------+-------------+ 
| 1 | SIMPLE  | MYTABLE | ALL | NULL   | NULL | NULL | NULL | 1448219 | Using where | 
+------+-------------+-----------+------+---------------+------+---------+------+---------+-------------+ 
1 row in set (0.00 sec) 

MariaDB [MYDB]> explain SELECT SQL_NO_CACHE * FROM MYDB.`MYTABLE` WHERE id = '123456789'; 
+------+-------------+-----------+-------+---------------+---------+---------+-------+------+-------+ 
| id | select_type | table  | type | possible_keys | key  | key_len | ref | rows | Extra | 
+------+-------------+-----------+-------+---------------+---------+---------+-------+------+-------+ 
| 1 | SIMPLE  | MYTABLE | const | PRIMARY  | PRIMARY | 18  | const | 1 |  | 
+------+-------------+-----------+-------+---------------+---------+---------+-------+------+-------+ 
1 row in set (0.00 sec) 

MariaDB [MYDB]> 

现在我的问题是,为什么

查询从服务器应用程序如下执行当我使用变量时,PRIMARY索引被忽略了吗?

我不知道这是性能下降的原因(因为在这个例子中,两个查询都返回0.00秒),但这让我皱眉为什么会有差异。我也在服务器应用程序中使用了一个准备好的查询和一个变量。所以我想检查一下这是否相关。

任何人都可以解释这一点吗?

+0

这是一个MySQL的问题。无论客户端工具如何,您都会得到相同的行为。 “ID”列的类型和索引定义是什么?你是否传递一个整数ID的文本参数值? MySQL可能会将* table *数据转换为参数类型而不是相反,从而强制进行全表扫描 –

+0

这确实是一个varchar字段(varchar(16)。在数据库中,以及在C#代码中设置的时候添加参数)。我可以补充说。 – Johan

+0

为什么使用'varchar(6)'字段来存储数字键?除了转换和空间问题,排序规则是不同的,'2'在'123456'后出现。 –

回答

1

索引长度表示id实际上是一个char/varchar列(VARCHAR(16)将是我的猜测)。

然后,问题是表或列的字符集与连接之一不匹配。考虑以下几点:

MariaDB [test]> create table t1 (id varchar(16) primary key, i int) charset latin1; 
Query OK, 0 rows affected (0.72 sec) 

MariaDB [test]> insert into t1 values ('123456789',1),('987654321',2); 
Query OK, 2 rows affected (0.08 sec) 
Records: 2 Duplicates: 0 Warnings: 0 

MariaDB [test]> set names latin1; 
Query OK, 0 rows affected (0.00 sec) 

MariaDB [test]> set @a='123456789'; 
Query OK, 0 rows affected (0.00 sec) 

MariaDB [test]> explain select * from t1 where id = @a; 
+------+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ 
| id | select_type | table | type | possible_keys | key  | key_len | ref | rows | Extra | 
+------+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ 
| 1 | SIMPLE  | t1 | const | PRIMARY  | PRIMARY | 18  | const | 1 |  | 
+------+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ 
1 row in set (0.00 sec) 

MariaDB [test]> set names utf8; 
Query OK, 0 rows affected (0.00 sec) 

MariaDB [test]> set @a='123456789'; 
Query OK, 0 rows affected (0.01 sec) 

MariaDB [test]> explain select * from t1 where id = @a; 
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+ 
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra  | 
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+ 
| 1 | SIMPLE  | t1 | ALL | NULL   | NULL | NULL | NULL | 2 | Using where | 
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+ 
1 row in set (0.00 sec) 

您需要任何修改表,或设置字符在会话中设置,或使用显式转换:

MariaDB [test]> explain select * from t1 where id = @a; 
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+ 
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra  | 
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+ 
| 1 | SIMPLE  | t1 | ALL | NULL   | NULL | NULL | NULL | 2 | Using where | 
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+ 
1 row in set (0.00 sec) 

MariaDB [test]> explain select * from t1 where id = convert(@a using latin1); 
+------+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ 
| id | select_type | table | type | possible_keys | key  | key_len | ref | rows | Extra | 
+------+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ 
| 1 | SIMPLE  | t1 | const | PRIMARY  | PRIMARY | 18  | const | 1 |  | 
+------+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ 
+0

转换变量确实使用INDEX。现在我需要检查我的服务器配置并查看那里使用了哪些字符集。谢谢! – Johan

+0

也许这会避免charset问题:'CAST(@a AS CHAR)' –

+0

我不这么认为。作为'CHAR'投射仍然会受到同样的连接字符集设置的影响:如果当前字符集错误,投射将无济于事;如果字符集正确,则不需要投射。 'CAST(@a AS BINARY)' - 更可能。 – elenst