您如何优化这个复杂的 sql 查询,然后选择正确的表索引

Posted

技术标签:

【中文标题】您如何优化这个复杂的 sql 查询,然后选择正确的表索引【英文标题】:How do you approach optimizing this complex sql query then choosing proper table index's 【发布时间】:2011-07-24 04:11:40 【问题描述】:

我有一个复杂的查询,我似乎无法使用适当的索引进行优化。关于如何修改查询或索引以进行优化的任何想法。 columndata a 表有 55000 条记录,drowsd 有 4000 条记录,其余表少于 25 条记录。你应该如何优化这样的问题:

SELECT DISTINCT a.id,
       a.string_data,
       b.grid_id, 
       c.data_type,    
       d.document_id
  FROM  `columndata` a, 
        `columngrid` b, 
        `dcolumns` c, 
        `drows` d  
  WHERE b.grid_id = 9 
    AND d.document_id = 17
    AND d.id = a.row_number   
    AND b.column_id = a.column_id
    AND c.id = a.column_id     
    AND 0 = (SELECT count(1) AS q
               FROM `security` e, 
                    `role_userlist` f,
                    `user_type_defaults`g                                        
              WHERE ((e.access_for = 1 
                AND e.access_for_id = 0)
                 OR (e.access_for = 2
                AND e.access_for_id = f.role_id
                AND f.userid = 0)
                 OR (e.access_for = 3
                 AND e.access_for_id = g.id
                 AND (g.usertype_name =""
                  OR (g.usertype_name = "Guest"
                 AND 0 = 0)))) 
                 AND e.access_level = 0
                 AND ((e.access_type = 2
                 AND e.access_subtype_grid_id = b.grid_id
                 AND e.access_subtype_column_id = a.column_id)                                                          
                  OR  (e.access_type = 4 
                 AND e.access_subtype_document_id = a.document_id
                 AND e.access_subtype_column_id = a.column_id))) 
ORDER BY d.ordering, b.ordering LIMIT 0, 330

表格

CREATE TABLE `columndata` (  
  `id` int(11) NOT NULL auto_increment,  
  `document_id` int (11) NOT NULL,  
  `column_id` int(11) NOT NULL,  
  `row_number` int(11) NOT NULL,  
  `string_data` varchar (5000),  
  PRIMARY KEY  (`id`),  
  INDEX(`column_id`,`row_number`,`document_id`),  
  INDEX(`row_number`),  
  INDEX(`document_id`)  
) ENGINE=MyISAM AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;  

CREATE TABLE `columngrid` (  
  `id` int(11) NOT NULL auto_increment,  
  `parent_id` int(11),  
  `column_id` int(11)  NOT NULL,  
  `grid_id` int(11) NOT NULL,  
  `ordering` int(11) NOT NULL,  
  PRIMARY KEY  (`id`),  
  INDEX (`parent_id`),  
  INDEX (`grid_id`,`column_id`,`ordering`)  
) ENGINE=MyISAM AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;  

CREATE TABLE `dcolumns` (  
  `id` int(11) NOT NULL auto_increment,  
  `header` varchar(25) NOT NULL,  
  `data_type` varchar (25) NOT NULL default 'T',                                                         
  PRIMARY KEY  (`id`)  
) ENGINE=MyISAM AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;  

CREATE TABLE `drows` (  
  `id` int(11) NOT NULL auto_increment,  
  `parent_id` int(11),  
  `document_id` int (11) NOT NULL,  
  `grid_id` int(11) NOT NULL,  
  `ordering` int(11) NOT NULL,  
  PRIMARY KEY  (`id`),  
  INDEX (`parent_id`),  
  INDEX (`document_id`,`id`,`ordering`)  
) ENGINE=MyISAM AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;  

CREATE TABLE `security` (    
  `id` int(11) NOT NULL auto_increment,    
  `access_for` int(11) NOT NULL,    
  `access_for_id` int(11) NOT NULL,  
  `access_type` int(11) NOT NULL,  
  `access_type_id` varchar(11) NOT NULL,  
  `access_subtype_grid_id` int(11) NULL,  
  `access_subtype_column_id` int(11) NULL,  
  `access_subtype_document_id` int(11) NULL,  
  `access_level` int(4) default 0,  
  PRIMARY KEY  (`id`),  
  INDEX `ind1` (`access_for`,`access_for_id`),  
  INDEX `ind2` (`access_type`,`access_type_id`),  
  INDEX `ind3` (`access_type`,`access_subtype_grid_id`,`access_subtype_column_id`),  
  INDEX `ind4` (`access_type`,`access_subtype_document_id`,`access_subtype_column_id`) 
) ENGINE=MyISAM AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;  

CREATE TABLE `role_userlist` (  
  `id` int(11) NOT NULL auto_increment,  
  `userid` int(11) NOT NULL,  
  `role_id` int(11) NOT NULL,  
  `userid_assigning_role` int(11) NOT NULL,  
  PRIMARY KEY  (`id`),  
  INDEX (`role_id`),  
  INDEX (`userid`)  
) ENGINE=MyISAM AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;  

CREATE TABLE `#__jgrid_user_type_defaults` (  
  `id` int(11) NOT NULL auto_increment,  
  `usertype_name` varchar(25) NOT NULL,  
  `access_level` int(11),  
  PRIMARY KEY  (`id`),  
  INDEX `ind1` (`usertype_name`)  
) ENGINE=MyISAM AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;  

【问题讨论】:

多年来我见过的最令人难以置信的查询 - 大声笑(sry) 【参考方案1】:

您是否尝试过使用表 JOINS 而不是编写无休止的 where 语句? 在单个查询中获取所有内容并不总是很方便,因为它会降低整体性能。

例如,我有一个复杂的搜索查询,它在编写为单个查询的 7 个不同表中查找值,搜索 20000 条记录大约需要 5 秒。将其拆分为 7 个较小的查询后,此类查询的总时间约为 0.2 秒。

【讨论】:

【参考方案2】:

优化查询时,首先查看您的数据库引擎生成的查询执行计划。

然后先看下你强迫可怜的数据库执行多少行,FROM子句:

FROM  `columndata` a, `columngrid` b, `dcolumns` c, `drows` d

指定四个表的连接。比如说,a=55,000 行,b=25 行,c=25 行,d=4,000 行。算一算。即展开集中的 137 BILLION 行。如果您没有非常小心积极地提早修剪行,您最终会得到一个运行速度非常慢的查询。

然后看看你的 WHERE 子句。如果不查看最后一个子句(这是一个大型子查询),您似乎将 b 限制为几行,而 d 可能限制为基于 document_id 的几百行。因此,您正在查看大约 55,000 x 5(比方说)x 5(比方说)x 500(比方说)= 690 百万行。

显然您的查询没有返回这么多行,所以它们都在最后一个子句中被过滤掉了。但是,WHERE 子句中的最后一个子句是一个子查询,它引用了另外三个 不相关 表。这意味着您正在强制数据库引擎循环遍历 6.9 亿行,在 EACH 上执行一个子查询(这是一个 COUNT 查询,最慢的一种),然后丢弃大多数 COUNT 0 的子查询。

现在你明白它为什么慢了吗?

我首先想到的优化是将0=(SELECT ... 子查询替换为NOT EXISTS (SELECT...,这会在找到一行后停止子查询执行,而不是强制数据库引擎计数所有这些,然后然后检查计数是否为零。你没有使用计数。您正在这里检查存在。使用EXISTS

其次,使第二个查询成为 FROM 子句的一部分(可能是 WITH 子句或只是放在那里)并将其与 columndata 表一起LEFT JOIN(在左侧),然后在 NULL(右侧)上选择(WHERE)。这通过仅保留子查询中不存在的行来修剪 columndata 表(最大的表)。数据库的查询优化器应该尝试用 JOIN 重写子查询,但我不确定 mysql 的优化器有多好。

【讨论】:

1.数据库并没有因为 ANSI 连接的发明而在一夜之间开始产生笛卡尔连接。 WHERE 子句中的老式联接与 INNER JOIN ON(...) 相同,除非特定查询形状与优化器中的旧代码匹配(Oracle cbo 代码库中就是这种情况)。 2. MySQL 不支持 WITH 子句。 3. MySQL 在重写半/反连接方面做得非常糟糕,这意味着对于更大的数据集,您必须使用 from 子句中的派生表手动重写它。 @Ronnis,我没有区分 ANSI 连接和 WHERE 的老式连接。在 ANSI SQL 标准化之前,我自己来自“老派”...... :-) 我也是。 2 年前进行了转换,从未回头。【参考方案3】:

感谢您的回复。首先,我删除了递归调用,并在我的 php 代码中首先找到了一些不显示的 column_numbers(整数)。然后我对结果数组进行“NOT IN”检查。请参阅下面的电话。这个调用给出了这个解释结果。

id select_type 表类型 possible_keys key key_len ref rows 过滤 1 SIMPLE b range grid_id grid_id 8 NULL 6 1000.00 使用 where;额外使用临时;使用文件排序 1 SIMPLE d ref PRIMARY document_id 4 const 4000 100.00 1 SIMPLE c eq_ref PRIMARY PRIMARY 4 b.column_id 1 100.00 1 SIMPLE a ref row_number column_id 8 c.id,d.id 1 100.00 使用 where

选择不同的 a.id, a.string_data, b.grid_id, c.data_type, d.document_id 来自columndataa, columngridb, dcolumnsc, drowsd 其中 b.grid_id = 9 AND d.document_id = 17 AND d.id = a.row_number AND b.column_id = a.column_id AND c.id = a.column_id AND a.column_id 不在 (0,3,6) ORDER BY d.ordering, b.ordering LIMIT 0, 330

我也尝试加入,但这要慢得多。欢迎评论如何使用正确的连接语法进行改进

首先选择行 (4000),然后将列与行 (50000) 匹配,同时删除隐藏的列,然后从列和 columgrid 表中添加额外数据 选择 t1.row_id t2.string_data, 2 AS grid_id, t5.data_type, t1.document_id (选择 DISTINCT a.id 作为 row_id, a.string_data, a.ordering 作为 row_ordering 发件人drows WHERE a.document_id = 7) t1 左连接(选择 DISTINCT b.row_id b.column_id b.string_data FROM dcolumns WHERE b.column_id 不在 (0,3,6)) t2 ON t1.id = t2.row_id 左连接(选择不同的 c.id, c.data_type) t3 ON t2.column_id = t3.id 左连接(选择 DISTINCT d.column_id, d.row_id, d.ordering 作为 column_ordering FROM columngrid) t4 ON(t1.id = t4.row_id AND t2.column_id = t4.column_id) ORDER BY t1.row_ordering, t4.column_ordering

【讨论】:

以上是关于您如何优化这个复杂的 sql 查询,然后选择正确的表索引的主要内容,如果未能解决你的问题,请参考以下文章

优化SQL查询:如何写出高性能SQL语句

如何优化这个嵌套的 SQL 查询

如何针对 db2 数据库优化 SQL/Python 选择查询?

SQL 查询优化

如何写出高性能的SQL语句

SQL查询优化Oracle