可以使用MySQL FIND_IN_SET或等价物来使用索引吗?

时间:2016-06-23 14:45:58

标签: mysql

如果我比较

explain select * from Foo where find_in_set(id,'2,3');
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | User  | ALL  | NULL          | NULL | NULL    | NULL |    4 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+

这一个

explain select * from Foo where id in (2,3);
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
|  1 | SIMPLE      | User  | range | PRIMARY       | PRIMARY | 8       | NULL |    2 | Using where |
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+

很明显FIND_IN_SET 利用主键。

我想将一个如上所述的查询放入存储过程中,并以逗号分隔的字符串作为参数。

有没有办法让查询的行为类似于使用索引的第二个版本,但是在编写查询时却不知道id设置的内容?

1 个答案:

答案 0 :(得分:2)

参考你的评论:

  

@MarcB数据库规范化,CSV字符串来自UI。   “获取以下人员的数据:101,202,303”

这个答案只关注那些用逗号分隔的数字。因为事实证明,你甚至都没有谈论FIND_IN_SET毕竟。

是的,你可以实现你想要的。您创建一个准备好的语句,接受一个字符串作为参数,就像我的Recent Answer一样。在该答案中,查看显示CREATE PROCEDURE的第二个块及其接受类似(1,2,3)的字符串的第二个参数。我马上就会回到这一点。

不是你需要看到它@spraff但其他人可能会。我们的任务是让type!= ALL,以及Explain的possible_keyskeys不显示null,正如您在第二个区块中所示。有关该主题的一般性阅读,请参阅文章Understanding EXPLAIN’s Output和名为EXPLAIN Extra Information的MySQL手册页。

现在,回到上面的(1,2,3)参考。我们从您的评论中了解到,您在问题中的第二个解释输出符合以下所需条件:

  1. type = range(特别是不是ALL)。请参阅上面的文档。
  2. key不为空
  3. 这些正是您在第二个Explain输出中的条件,以及可通过以下查询看到的输出:

    explain 
    select * from ratings where id in (2331425, 430364, 4557546, 2696638, 4510549, 362832, 2382514, 1424071, 4672814, 291859, 1540849, 2128670, 1320803, 218006, 1827619, 3784075, 4037520, 4135373, ... use your imagination ..., ...,  4369522, 3312835);
    

    我在in子句列表中有999个值。这是我在附录D中来自this answer的样本,而不是生成这样一个随机的csv字符串,由开括号和近括号括起来。

    请注意下面的999元素的以下说明输出:

    enter image description here

    目标实现。您可以使用this link使用PREPARED STATEMENT使用类似于我前面提到的存储过程来实现此目的(这些事情使用concat()后跟EXECUTE)。

    使用索引,没有经历Tablescan(意思是坏)。进一步的读数是The range Join Type,你可以在MySQL的基于成本的优化器(CBO)上找到任何参考,这个来自vladr的answer虽然过时,但是关注ANALYZE TABLE部分,特别是在重大数据变化。请注意,ANALYZE可能需要花费大量时间才能在超大型数据集上运行。有时候很多个小时。

    Sql注入攻击:

    使用传递给存储过程的字符串是SQL注入攻击的攻击媒介。在使用用户提供的数据时,必须采取预防措施以防止它们发生。如果您的例程适用于您自己的系统生成的id,那么您就是安全的。但请注意,当数据由先前插入或更新中未清理该数据的例程放置到位时,会发生第二级SQL注入攻击。攻击通过数据预先存在并在以后使用(一种定时炸弹)。

    所以这个答案大部分都是已完成

    下面是同一个表的视图,对其进行了少量修改,以显示先前查询中可怕的Tablescan 的外观(但针对名为{{1的非索引列) }})。

    查看我们当前的表格定义:

    thing

    请注意,列CREATE TABLE `ratings` ( `id` int(11) NOT NULL AUTO_INCREMENT, `thing` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5046214 DEFAULT CHARSET=utf8; select min(id), max(id),count(*) as theCount from ratings; +---------+---------+----------+ | min(id) | max(id) | theCount | +---------+---------+----------+ | 1 | 5046213 | 4718592 | +---------+---------+----------+ 之前是可以为空的int列。

    thing

    然后解释是一个Tablescan(针对列update ratings set thing=id where id<1000000; update ratings set thing=id where id>=1000000 and id<2000000; update ratings set thing=id where id>=2000000 and id<3000000; update ratings set thing=id where id>=3000000 and id<4000000; update ratings set thing=id where id>=4000000 and id<5100000; select count(*) from ratings where thing!=id; -- 0 rows ALTER TABLE ratings MODIFY COLUMN thing int not null; -- current table definition (after above ALTER): CREATE TABLE `ratings` ( `id` int(11) NOT NULL AUTO_INCREMENT, `thing` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5046214 DEFAULT CHARSET=utf8; ):

    enter image description here