在批量查询的时候,有时为了提高检索效率,会使用Unique配合IN查询。此时注意IN中不要写子查询,否则时间复杂度会大大增加。不过对于已经确定的检索范围,如果有Unique索引,效率还是可以的。

        但有时候由于种种原因,并不总会有Unique索引。那么当表的数据量比较大(达到千万级)的时候,查询的时间就比较长了。因为IN会进行全表搜索。而且如果想去重的话,那就要配合Group By语句了。

        已知的改进的方案有若干种:①用OR取代IN②在WHERE子句中限定IN查询范围的上下限,③用UNION的方式合并查询结果,等等。

        如果单纯用UNION执行单次查询的话,查询的效率通常还是会受到影响。它还是会进行全表检索,而且会检索多次。而且,如果是单次检索,可能会构造出行数很多的SQL语句,MYSQL服务器解析SQL语句并处理的时候也会慢下来。

        下面这种改进方案是基于查询范围上限进行的。

        ①在应用程序中(如JVM),将查询范围的集合元素按升(或降,后面注意相应调整)序排序。

        ②在MYSQL中建立两个函数:func_get_split_string,用来截取字符串;func_get_split_string_total,获取被分隔符(如',')分隔的查询元素的总数。如下:

-- 截取字符串
DROP FUNCTION IF EXISTS func_get_split_string;
DELIMITER $$
CREATE DEFINER=`root`@`%` FUNCTION `func_get_split_string`(
f_string varchar(16382),f_delimiter varchar(5),f_order int) RETURNS varchar(255) CHARSET utf8
BEGIN
 -- Get the separated number of given string.
 declare result varchar(255) default '';
 set result = reverse(substring_index(reverse(substring_index(f_string,f_delimiter,f_order)),f_delimiter,1));
 return result;
END$$
DELIMITER ;


-- 元素总数
DROP FUNCTION IF EXISTS func_get_split_string_total;
DELIMITER $$
CREATE DEFINER=`root`@`%` FUNCTION `func_get_split_string_total`(
f_string varchar(16382),f_delimiter varchar(5)
) RETURNS int(11)
BEGIN
 -- Get the total number of given string.
 return 1+(length(f_string) - length(replace(f_string,f_delimiter,'')));
END$$
DELIMITER ;

        ②写一个存储过程,入参为IN的结果集用一个特定的字符拼接而成的(因为Mysql的存储过程不支持集合)。如果搜索集元素较多或较长,参数类型应设为TEXT或LONGTEXT。这是入口存储过程。

        ④在入口存储过程中,用函数func_get_split_string拆解输入的范围的元素,然后在循环中调用执行子任务的存储过程。

        

DROP PROCEDURE IF EXISTS Pro_entry;
DELIMITER $$
CREATE PROCEDURE Pro_entry(
    _cIds TEXT) -- 定义参数
BEGIN
    DECLARE cnt int default 0;
    DECLARE i int default 0;
    DECLARE _cId VARCHAR(128);//当前元素
    DECLARE _priorCid VARCHAR(128) DEFAULT NULL;//前一元素

    SET cnt = func_get_split_string_total(_cIds,',');//获得元素个数
    while i < cnt
    do
        set i = i + 1;
        SET _cId = func_get_split_string(_cIds,',',i);
        CALL Proc_sub(_cId, _priorCid);//调用子存储过程进行查询操作
    END WHILE;

END$$
DELIMITER ;

        ⑤定义执行存储过程Proc_sub,可用Limit限制每次的检索行数。注意用到动态指令PREPARE、EXECUTE执行:        

DROP PROCEDURE IF EXISTS Pro_sub;
DELIMITER $$
CREATE PROCEDURE Pro_sub (
    INOUT annualAssetsSql LONGTEXT,
    IN _cId VARCHAR ( 128 ),
    IN _priorCid VARCHAR ( 128 )
)
BEGIN -- 按照Sql的逻辑层次排布代码
    DECLARE thisSql LONGTEXT;

    SET thisSql = CONCAT(
        ' SELECT * FROM 【表名】 tb
        WHERE tb.c_id = ''', _cId, ''' '
    );
    IF _priorCid IS NOT NULL THEN
        SET thisSql = CONCAT(thisSql, ' AND tb.c_id > ''', _priorCid, ''' '); 
    END IF;
    SET thisSql = CONCAT(thisSql, ' LIMIT 1 )');

    set @annual_assets_sql = thisSql;
    PREPARE testSelectTemp FROM @annual_assets_sql;
    EXECUTE testSelectTemp;
    DEALLOCATE PREPARE testSelectTemp;
END$$
DELIMITER ;

确定了上下限的情况下配合IN使用。但这也是会产生较大的SQL语句。