【问题标题】:Converting a UNION query in MySQL在 MySQL 中转换 UNION 查询
【发布时间】:2010-09-14 21:46:17
【问题描述】:

我有一个非常大的表 (8gb),其中包含有关文件的信息,我需要针对它运行一个看起来像这样的报告:

(select * from fs_walk_scan where file_path like '\\\\server1\\groot$\\%' order by file_size desc limit 0,30)
UNION ALL
(select * from fs_walk_scan where file_path like '\\\\server1\\hroot$\\%' order by file_size desc limit 0,30)
UNION ALL
(select * from fs_walk_scan where file_path like '\\\\server1\\iroot$\\%' order by file_size desc limit 0,30)
UNION ALL
(select * from fs_walk_scan where file_path like '\\\\server2\\froot$\\%' order by file_size desc limit 0,30)
UNION ALL
(select * from fs_walk_scan where file_path like '\\\\server2\\groot$\\%' order by file_size desc limit 0,30)
UNION ALL
(select * from fs_walk_scan where file_path like '\\\\server3\\hroot$\\%' order by file_size desc limit 0,30)
UNION ALL
(select * from fs_walk_scan where file_path like '\\\\server4\\iroot$\\%' order by file_size desc limit 0,30)
UNION ALL
(select * from fs_walk_scan where file_path like '\\\\server5\\iroot$\\%' order by file_size desc limit 0,30)
[...]
order by substring_index(file_path,'\\',4), file_size desc

此方法完成了我需要做的事情:获取每个卷的 30 个最大文件的列表。但是,这非常慢,并且“喜欢”搜索是硬编码的,即使它们位于另一个表中并且可以通过这种方式获得。

我正在寻找一种无需多次通过巨大桌子即可完成此操作的方法。有人有什么想法吗?

谢谢。

附:我无法以任何方式更改巨大源表的结构。

更新:file_path 和 file_size 上有索引,但是这些子(?)查询中的每一个仍然需要大约 10 分钟,我必须至少做 22 分钟。

【问题讨论】:

    标签: sql mysql dynamic union


    【解决方案1】:

    您对该表有哪些类型的索引?这个索引:

    创建索引 fs_search_idx ON fs_walk_scan(file_path, file_size desc)

    将大大加快此查询的速度...如果您还没有类似的查询。

    更新:

    您说file_path 和file_size 上已经有索引...它们是单独的索引吗?或者是否有一个索引将两列索引在一起?对于此查询,差异将是巨大的。即使有 22 个子查询,如果索引正确,这应该很快。

    【讨论】:

    • 我认为在不允许更改数据库结构的情况下这是不可能的
    • 它们是独立的,但我不明白将它们连接在一起会有什么帮助
    • 基本上,您正在搜索 file_path,然后在其中选择 30 个最大的文件(使用 LIMIT 和 ORDER BY)。因此,您实际上是在同时搜索两列。如果您要搜索的两条数据一起在索引中可用,则搜索速度很快。
    【解决方案2】:

    你可以使用正则表达式:

    select * from fs_walk_scan
      where file_path regexp '^\\\\server(1\\[ghi]|2\\[fg]|3\\h|[45]\\i)root$\\'
    

    否则,如果你可以修改你的表结构,添加两列来保存服务器名称和基本路径(并索引它们),这样你就可以创建一个更简单的查询:

    select * from fs_walk_scan
      where server = 'server1' and base_path in ('groot$', 'hroot$', 'iroot$')
         or server = 'server2' and base_path in ('froot$', 'groot$')
    

    您可以设置触发器以在插入记录时初始化字段,或者在之后进行批量更新以填充两个额外的列。

    【讨论】:

      【解决方案3】:

      你可以做这样的事情......假设 fs_list 有一个你的“LIKE”搜索列表:

      DELIMITER $$
      
      DROP PROCEDURE IF EXISTS `test`.`proc_fs_search` $$
      CREATE PROCEDURE `test`.`proc_fs_search` ()
      BEGIN
      
      DECLARE cur_path VARCHAR(255);
      DECLARE done INT DEFAULT 0;
      
      
      DECLARE list_cursor CURSOR FOR select file_path from fs_list;
      
      DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
      
      SET @sql_query = '';
      
      OPEN list_cursor;
      
      REPEAT
        FETCH list_cursor INTO cur_path;
      
        IF NOT done THEN
          IF @sql_query <> '' THEN
            SET @sql_query = CONCAT(@sql_query, ' UNION ALL ');
          END IF;
      
          SET @sql_query = CONCAT(@sql_query, ' (select * from fs_walk_scan where file_path like ''', cur_path , ''' order by file_size desc limit 0,30)');
        END IF;
      
      UNTIL done END REPEAT;
      
      SET @sql_query = CONCAT(@sql_query, ' order by file_path, file_size desc');
      
      PREPARE stmt FROM @sql_query;
      EXECUTE stmt;
      DEALLOCATE PREPARE stmt;
      
      END $$
      
      DELIMITER ;
      

      【讨论】:

        【解决方案4】:

        试试这个。
        您希望获取少于 30 条记录且文件大小更大且文件路径相同的每条记录。

        SELECT * 
        FROM   fs_walk_scan a
        WHERE  ( SELECT COUNT(*) 
                 FROM   fs_walk_scan b 
                 WHERE  b.file_size  > a.file_size 
                 AND    b.file_path  = a.file_path
               ) < 30
        

        编辑:

        显然这表现得像条狗。那么......这个循环语法怎么样?

        SELECT DISTINCT file_path
        INTO tmp1
        FROM   fs_walk_scan a
        
        DECLARE path VARCHAR(255);
        
        SELECT MIN(file_path)
        INTO   path
        FROM   tmp1 
        
        WHILE  path IS NOT NULL DO
            SELECT * 
            FROM   fs_walk_scan
            WHERE  file_path = path
            ORDER BY file_size DESC
            LIMIT 0,30
        
            SELECT MIN(file_path)
            INTO   path
            FROM   tmp1
            WHERE  file_path > path 
        END WHILE
        

        这里的想法是 1.获取文件路径列表 2. 循环,对每个路径进行查询,将获得 30 个最大的文件大小。

        (我确实查过语法,但我对 MySQL 不是很感兴趣,所以如果它不在那里,请多多指教。请随时编辑/评论)

        【讨论】:

        • 尝试了上述方法。让查询运行了 48 小时(意外),但仍然没有返回结果。
        • 哎哟。那让我走开想想吧!如果我想出更好的东西,我会编辑我的帖子。
        • 感谢您的帮助,我真的很感激。但是我不认为这种更新的方法会改变任何性能。问题是我们仍然在 while 循环的每次迭代中执行“select * from fs_walk_scan”,这非常昂贵,因为表很大。
        • 我能说什么?桌子很大。它会很慢。另一种策略是使用触发器维护“前 30 名”列表,但这并不会真正提高效率,而且会减慢插入速度。
        • 你是对的,无论如何它都会很慢。但是我认为可能有一种方法可以做到这一点。我现在想的是使用 perl(但最终是本机)脚本一次遍历表格一行,并根据其路径 loc 的前 30 个列表中的最低值测试该行的文件大小。
        【解决方案5】:

        这样的东西怎么样(还没有测试过,但看起来很接近):

        select * from fs_walk_scan where file_path like '\\\\server' and file_path like 'root$\\%' order by file_size desc 
        

        通过这种方式,您可以对单个字段进行一对比较,这些比较通常与您所描述的内容相匹配。也可以使用正则表达式,但我没有这样做。

        【讨论】:

        • 您是建议在每个 UNIONed 查询中执行此操作以提高性能(因为这可能会有所帮助)还是我误解了?
        【解决方案6】:

        您可以使用分组和自加入来实现此目的。

        SELECT substring_index(file_path, '\\', 4), file_path
        from fs_walk_scan as ws1
        WHERE 30<= (
        select count(*) from fs_Walk_scan as ws2
        where substring_index(ws2.file_path, '\\', 4) = substring_index(ws1.file_path, '\\', 4)
        and ws2.file_size > ws1.file_size
        and ws2.file_path <> ws1.file_path)
        group by substring_index(file_path, '\\', 4)
        

        它仍然是一个 O(n) 查询(n 是组数),但更灵活、更短。

        编辑: 另一种方法是使用变量。您的目的的可行性将取决于您将如何运行此查询。

        set @idx=0; set @cur_vol=0;                                                                      
        SELECT file_volume, file_path, file_size FROM (
            SELECT file_volume, file_path, file_size,
            IF(@cur_vol != a.file_volume, @idx:=1, @idx:=@idx+1) AS row_index,
            IF(@cur_vol != a.file_volume, @cur_vol:=a.file_volume, 0) AS discard
            FROM (SELECT substring_index(file_path, '\\', 4) as file_volume, file_path, file_size 
                FROM fs_walk_scan
                ORDER BY substring_index(file_path,'\\',4), file_size DESC) AS a
            HAVING row_index <= 30) AS b;
        

        我还没有尝试过这段代码,但是变量的概念可以像这样用于你的目的。

        【讨论】:

          猜你喜欢
          • 2011-04-01
          • 1970-01-01
          • 2012-12-18
          • 2012-08-20
          • 1970-01-01
          • 1970-01-01
          • 2021-11-20
          • 1970-01-01
          • 2012-08-08
          相关资源
          最近更新 更多