您如何优化这个复杂的 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 条记录,drows
d 有 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
来自columndata
a,
columngrid
b,
dcolumns
c,
drows
d
其中 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 查询,然后选择正确的表索引的主要内容,如果未能解决你的问题,请参考以下文章