我有以下JOIN查询:
SELECT
table1.*,
table2.*
FROM
Table1 AS table1
LEFT JOIN
Table2 AS table2
USING
(col1)
LEFT JOIN
Table3 as table3
USING
(col1)
WHERE
3963.191 *
ACOS(
(SIN(PI() * $usersLatitude / 180) * SIN(PI() * table3.latitude / 180))
+
(COS(PI() * $usersLatitude / 180) * COS(PI() * table3.latitude / 180) * COS(PI() * table3.longitude / 180 - PI() * 37.1092162 / 180))
) <= 10
AND
table1.col1 != '1'
AND
table1.col2 LIKE 'A'
AND
(table1.col3 LIKE 'X' OR table1.col3 LIKE 'X-Y')
AND
(table2.col4 = 'Y' OR table2.col5 = 'Y')
// Data Types of all columns in the query:
// col1: int(11)
// col2: char(1)
// col3: varchar(3)
// col4: char(1)
// col5: char(1)
// col6: int(11)
// latitude: varchar(25)
// longitude: varchar(25)
// All 3 tables (table1, table2, and table3) are `MyISAM`.
它在 0.15秒下执行。
但是,如果我只是添加:
ORDER BY
table1.col6 DESC
它会在 3秒中执行。
查询中的所有列都是索引的,包括
table1.col6
中使用的ORDER BY
。以下是
EXPLAIN EXTENDED
(不带 ORDER BY
)的结果:id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE table1 ALL PRIMARY,col2,col3 NULL NULL NULL 140101 72.61 Using where
1 SIMPLE table2 eq_ref PRIMARY,col4,col5 PRIMARY 4 table1.col1 1 100 Using where
1 SIMPLE table3 eq_ref PRIMARY PRIMARY 4 table1.col1 1 100 Using where
这是
EXPLAIN EXTENDED
和 ORDER BY
的结果:id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE table1 ALL PRIMARY,col2,col3 NULL NULL NULL 140101 72.61 Using where; Using filesort
1 SIMPLE table2 eq_ref PRIMARY,col4,col5 PRIMARY 4 table1.col1 1 100 Using where
1 SIMPLE table3 eq_ref PRIMARY PRIMARY 4 table1.col1 1 100 Using where
奇怪的是,我在此站点上的其他几个查询中使用了
ORDER BY DESC
,并且它的速度不会像使用此特定查询那样慢。此查询有一些特定的原因,导致它的ORDER BY
大大降低了速度。我还在所有3个表上做了一个
ANALYZE TABLE
,它们都报告了OK
。然后,我用LIKE
替换了查询中的每个=
,它实际上使没有ORDER BY
的查询从 0.2秒变为 3秒。换句话说,用LIKE
替换=
会使原始查询花费的时间与添加ORDER BY
一样长!考虑到LIKE
比=
做更多的工作,怎么可能?也许这就是为什么ORDER BY
花费这么长时间的线索?这里是我尝试过的东西(不成功):
1)我没有尝试
SELECT table1.*, table2.*
而不是SELECT table1.col1
,但它仍然花费了 3秒的时间来完成。2)我尝试在
col1
中的col2
,col3
,col6
和Table1
上添加复合索引,但是它没有提高执行速度。3)我尝试使用this solution将该查询作为子查询,然后在最后将
ORDER BY
包装在其外部,但这并没有提高执行速度。4)我尝试了以下版本的查询,但没有任何改善,实际上使查询接管了 3秒,甚至没有向其中添加
ORDER BY
(也许提供了另一个线索):SELECT STRAIGHT_JOIN
T1.*,
T2.*
FROM
Table1 AS T1
JOIN Table2 AS T2
ON T1.Col1 = T2.Col1
AND ( T2.Col4 = 'Y' OR T2.Col5 = 'Y' )
JOIN Table3 as T3
ON T1.Col1 = T3.Col1
AND 3963.191
* ACOS( (SIN(PI() * $usersLatitude / 180) * SIN(PI() * T3.latitude / 180))
+ ( COS(PI() * $usersLatitude / 180) * COS(PI() * T3.latitude / 180)
* COS(PI() * table3.longitude / 180 - PI() * 37.1092162 / 180)
)
) <= 10
WHERE
T1.Col2 LIKE 'A'
AND ( T1.col3 LIKE 'X' OR T1.col3 LIKE 'X-Y')
AND T1.Col1 != '1'
ORDER BY
T1.Col6
// With the following composite indexes:
// On Table 1, index on ( Col2, Col3, Col1, Col6 )
// On Table 2, index on ( Col1, Col4, Col5 )
// Remember, all individual columns are already indexed.
...
如何使用
ORDER BY
使这个顽固的查询快速运行? 还是那不可能?编辑:
所有3个表的
SHOW CREATE TABLE
结果:CREATE TABLE `Table1` (
`col1` int(11) unsigned NOT NULL AUTO_INCREMENT,
`col100` varchar(25) CHARACTER SET utf8 DEFAULT NULL,
`col101` varchar(60) COLLATE utf8_bin DEFAULT NULL,
`col102` varchar(50) CHARACTER SET utf8 DEFAULT NULL,
`col103` varchar(10) COLLATE utf8_bin DEFAULT '00000000',
`col104` date NOT NULL,
`col105` int(3) DEFAULT NULL,
`col106` varchar(25) COLLATE utf8_bin DEFAULT NULL,
`col107` varchar(20) COLLATE utf8_bin DEFAULT 'Blah',
`col108` varchar(2) COLLATE utf8_bin DEFAULT 'No',
`col109` varchar(15) COLLATE utf8_bin DEFAULT 'Blah',
`col2` enum('A','B') COLLATE utf8_bin DEFAULT NULL,
`col3` enum('A','B','A-B') COLLATE utf8_bin DEFAULT NULL,
`col110` decimal(10,7) NOT NULL DEFAULT '0.0000000',
`col111` decimal(10,7) NOT NULL DEFAULT '0.0000000',
`col112` char(1) COLLATE utf8_bin DEFAULT 'N',
`col113` char(1) COLLATE utf8_bin DEFAULT 'N',
`col114` int(11) DEFAULT NULL,
`col115` varchar(15) COLLATE utf8_bin DEFAULT 'Blah',
`col6` int(11) DEFAULT NULL,
`col117` varchar(45) COLLATE utf8_bin DEFAULT NULL,
`col118` varchar(2) COLLATE utf8_bin NOT NULL,
`col119` tinyint(2) NOT NULL,
`col120` int(6) NOT NULL,
`col121` varchar(7) COLLATE utf8_bin NOT NULL,
`col122` varchar(6) COLLATE utf8_bin NOT NULL,
`col123` char(1) COLLATE utf8_bin NOT NULL DEFAULT 'A',
`col124` varchar(200) COLLATE utf8_bin NOT NULL,
`col125` tinyint(4) NOT NULL,
`col126` tinyint(1) NOT NULL,
`col127` varchar(1) COLLATE utf8_bin NOT NULL DEFAULT 'A',
`col128` tinyint(1) NOT NULL DEFAULT '0',
`col129` smallint(5) unsigned NOT NULL,
`col130` varchar(1) COLLATE utf8_bin NOT NULL DEFAULT 'A',
`col131` int(11) NOT NULL,
`col132` tinyint(1) NOT NULL,
`col133` tinyint(1) NOT NULL,
`col134` varchar(1) COLLATE utf8_bin NOT NULL,
`col135` varchar(200) COLLATE utf8_bin NOT NULL,
`col136` int(11) NOT NULL,
`col137` int(10) unsigned NOT NULL,
`col138` int(11) NOT NULL,
`col139` tinyint(1) NOT NULL,
`col140` tinyint(1) NOT NULL,
`col141` tinyint(4) NOT NULL,
`col142` varchar(25) COLLATE utf8_bin NOT NULL,
`col143` varchar(25) COLLATE utf8_bin NOT NULL,
`col144` tinyint(1) unsigned NOT NULL,
`col145` tinyint(4) NOT NULL,
PRIMARY KEY (`col1`),
KEY `col2` (`col2`),
KEY `col3` (`col3`),
KEY `CompositeIndex0` (`col1`,`col2`,`col3`,`col6`),
KEY `CompositeIndex1` (`col2`,`col3`,`col1`,`col6`),
KEY `idx01` (`col1`,`col2`,`col3`)
[19 other indexes that do not involve col1, col2, col3, or col6...]
) ENGINE=MyISAM AUTO_INCREMENT=160640 DEFAULT CHARSET=utf8 COLLATE=utf8_bin
//*******************************************************//
CREATE TABLE `Table2` (
`col1` int(11) unsigned NOT NULL DEFAULT '0',
`col201` varchar(45) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT 'Blah',
`col202` varchar(45) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT 'Blah',
`col203` varchar(45) COLLATE utf8_bin DEFAULT 'Blah',
`col204` varchar(45) COLLATE utf8_bin DEFAULT 'Blah',
`col205` varchar(45) COLLATE utf8_bin DEFAULT 'Blah',
`col206` varchar(45) COLLATE utf8_bin DEFAULT 'Blah',
`col207` varchar(45) COLLATE utf8_bin DEFAULT 'Blah',
`col208` varchar(45) COLLATE utf8_bin DEFAULT 'Blah',
`col209` varchar(45) COLLATE utf8_bin DEFAULT 'Blah',
`col210` varchar(45) COLLATE utf8_bin DEFAULT 'Blah',
`col211` varchar(45) COLLATE utf8_bin DEFAULT 'Blah',
`col212` varchar(45) COLLATE utf8_bin DEFAULT 'Blah',
`col213` varchar(45) COLLATE utf8_bin DEFAULT 'Blah',
`col214` varchar(45) COLLATE utf8_bin DEFAULT 'Blah',
`col215` varchar(45) COLLATE utf8_bin DEFAULT 'Blah',
`col216` varchar(45) COLLATE utf8_bin DEFAULT 'Blah',
`col217` varchar(45) COLLATE utf8_bin DEFAULT 'Blah',
`col218` varchar(45) COLLATE utf8_bin DEFAULT 'Blah',
`col219` varchar(45) COLLATE utf8_bin DEFAULT 'Blah',
`col220` varchar(255) COLLATE utf8_bin DEFAULT 'Blah',
`col221` varchar(255) COLLATE utf8_bin DEFAULT 'Blah',
`col222` varchar(255) COLLATE utf8_bin DEFAULT 'Blah',
`col223` varchar(255) COLLATE utf8_bin DEFAULT 'Blah',
`col224` varchar(45) COLLATE utf8_bin DEFAULT ‘Blah’,
`col225` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`col4` char(1) COLLATE utf8_bin DEFAULT 'A',
`col226` char(1) COLLATE utf8_bin DEFAULT 'A',
`col227` varchar(5) COLLATE utf8_bin DEFAULT 'Blah',
`col228` char(1) COLLATE utf8_bin NOT NULL,
`col229` text COLLATE utf8_bin,
`col5` char(1) COLLATE utf8_bin DEFAULT 'A',
`col230` varchar(255) COLLATE utf8_bin DEFAULT 'Blah',
`col231` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`col232` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`col233` varchar(255) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`col1`),
KEY `col4` (`col4`),
KEY `col5` (`col5`),
KEY `CompositeIndex1` (`col1`,`col4`,`col5`),
[4 other indexes not involving col1, col4, col5...]
FULLTEXT KEY `col220` (`col220`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin
//*******************************************************//
CREATE TABLE `Table3` (
`col1` int(11) unsigned NOT NULL DEFAULT '0',
`col300` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`latitude` varchar(25) COLLATE utf8_bin NOT NULL DEFAULT '0',
`longitude` varchar(25) COLLATE utf8_bin NOT NULL DEFAULT '0',
`col301` int(11) DEFAULT NULL,
`static2` float(18,16) DEFAULT '0.0000000000000000',
`static3` float(18,16) DEFAULT '0.0000000000000000',
PRIMARY KEY (`col1`),
KEY `latitude` (`latitude`),
KEY `longitude` (`longitude`),
KEY `static2` (`static2`),
KEY `static3` (`static3`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin
编辑2:
以下是我的 MySQL配置文件。除其他事项外,请注意
sort-buffer-size
如何设置为1M
。根据this,不应将其设置在256K
以上,否则它实际上会使速度降低“37x”。 可能是问题的一部分吗? # The MySQL database server configuration file.
[mysqld]
open-files-limit = 20000
thread-cache-size = 16
table-open-cache = 2048
table-definition-cache = 512
query-cache-type = 1
query-cache-size = 32M
query-cache-limit = 1M
sort-buffer-size = 1M
read-buffer-size = 1M
read-rnd-buffer-size = 8M
join-buffer-size = 1M
tmp-table-size = 64M
max-heap-table-size = 64M
back-log = 100
max-connections = 200
max-connect-errors = 10000
max-allowed-packet = 16M
interactive-timeout = 600
wait-timeout = 180
net_read_timeout = 30
net_write_timeout = 30
back_log = 128
myisam-sort-buffer-size = 128M
innodb-buffer-pool-size = 320M
innodb-log-buffer-size = 4M
innodb-log-file-size = 128M
innodb-log-files-in-group = 2
innodb-file-per-table = 1
[mysqldump]
max-allowed-packet = 16M
另一方面,这是IVAN最新查询中
EXPLAIN EXTENDED
的结果:id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE T1 ref PRIMARY,col2,col3,col1,CompositeIndex1,idx01 CompositeIndex1 2 const 92333 Using where; Using filesort
1 SIMPLE T3 eq_ref PRIMARY PRIMARY 4 T1.col1 1 Using where
1 SIMPLE T2 eq_ref PRIMARY,CompositeIndex1,idx_static1 PRIMARY 4 T1.col1 1 Using where
在另一件事上,这很奇怪。以下带有
ORDER BY
的查询版本仅需 0.2秒即可完成:SELECT STRAIGHT_JOIN T1 . * , T2 . *
FROM Table3 AS T3
JOIN Table2 AS T2 ON T3.col1 = T2.col1
AND (
T2.col4 = 'Y'
OR T2.col5 = 'Y'
)
JOIN Table1 AS T1 ON T3.col1 = T1.col1
AND 3963.191 * ACOS( (
SIN( PI( ) * - 87.8819594 /180 ) * SIN( PI( ) * T3.latitude /180 ) ) + ( COS( PI( ) * - 87.8819594 /180 ) * COS( PI( ) * T3.latitude /180 ) * COS( PI( ) * T3.longitude /180 - PI( )* 37.1092162 /180 ) )
) <=10
WHERE T1.col2 LIKE 'A'
AND (
T1.col3 LIKE 'X'
OR T1.col3 LIKE 'X-Y'
)
AND T1.col1 != '1'
ORDER BY T1.col6 DESC
基本上,此版本的查询执行
FROM Table3 AS T3
和JOIN
表1和2,而原始查询执行FROM Table1 AS T1
和JOIN
表2和3。这是上述查询的
EXPLAIN EXTENDED
:id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE T3 ALL PRIMARY NULL NULL NULL 141923 100 Using where; Using temporary; Using filesort
1 SIMPLE T2 eq_ref PRIMARY,col4,col5,CompositeIndex1 PRIMARY 4 T3.col1 1 100 Using where
1 SIMPLE T1 eq_ref PRIMARY,col2,col3,col1,CompositeIndex1,idx01 PRIMARY 4 T2.col1 1 100 Using where
请注意,此查询实际上是如何对Ivan的原始查询和新查询同时执行
filesort
和temporary
的。 怎么会快10倍? 甚至更陌生的是,切换
filesort
的顺序似乎也不会改善Ivan的原始查询或较新的查询。 为什么?
最佳答案
好,
我建议您重新设置查询样式:
AND(T1.col3 LIKE'X'或T1.col3 LIKE'X-Y')
AND T1.col3 IN('X','X-Y')
创建一些新列以存储:
SIN(PI() * T3.latitude / 180)
COS(PI() * table3.longitude / 180 - PI() * 37.1092162 / 180)
COS(PI() * T3.latitude / 180)
SIN(PI()* $ usersLatitude / 180)
COS(PI()* $ usersLatitude / 180)
mysql query index hint
进一步添加
为了删除:
( T2.Col4 = 'Y' OR T2.Col5 = 'Y' )
在这种情况下,您不能使用 IN ,因此请创建一个新列,该列是该表达式的结果。
alter table table2 add static1 bit default 0;
alter table add index idx_static1(static1);
update table2 t2 set static1=1 where ( T2.Col4 = 'Y' OR T2.Col5 = 'Y' );
alter table table3 add static2 float(18,16) default 0;
update table3 set static2=SIN(PI() * T3.latitude / 180) where 1
alter table table3 add static3 float(18,16) default 0;
update table3 set static3 = COS(PI() * T3.latitude / 180) * COS(PI() * table3.longitude / 180 - PI() * 37.1092162 / 180) where 1
如果 table1.col2 值很少
alter table table1 change col2 col2 enum('A','B','C');
如果 table1.col3 值很少
alter table table1 change col3 col3 enum('X','Y','X-Y');
为涉及以下内容的所有列创建唯一索引
更改表添加索引idx01(col1,col2,col3)
SELECT STRAIGHT_JOIN
T1.*,
T2.*
FROM
Table1 AS T1
JOIN Table2 AS T2 ON T1.Col1 = T2.Col1
JOIN Table3 as T3 ON T1.Col1 = T3.Col1
WHERE static1=1 AND
T1.Col2 = 'A'
AND T1.col3 IN ( 'X', 'X-Y')
AND T1.Col1 != 1
AND ACOS(
(
$usersLatitude_sin_pi_fract180 * t3.static2
+ $usersLatitude_cos_pi_fract180 * t3.static3
)
) <= 0,00252321929476 -- this's 10/3963.191
ORDER BY T1.Col6
您的评论建议我您在查询中使用了不同的排序规则(col1是latin1_swedish,而col2是utf8),或者您的连接使用了其他排序规则(您的连接是utf-8,并且查询了latin1_german列),因此在查询时:
t1.col2 = 'A'
Mysql必须将每个值从utf-8转换为latin1。
另请参见mysql文档的collate section。
一种快速的方法是将所有(列,表,服务器,连接,客户端)转换为相同的排序规则singel字节,如果您不需要utf-8会更好。
请小心我可能做的类型错误或语法错误。
进一步添加2
我在测试数据库上重新创建了表,并修复了以下列:
t1.col2,t2.col3 不能为空,t1.col1是主要的,不能为null。
索引“t1.CompositeIndex1”应仅索引:col2,col3,col1;为“order by”列编制索引是不充分的或最糟糕的。
我创建了static1,并在t2.col1和t2.static1上创建了一个索引,但是我的DB中的6行未使用(请参阅后面的说明)。 t2.static1也不能为空。
我还将查询调整为各列的排序规则:
SELECT T1.*, T2.*
FROM Table1 AS T1
JOIN Table2 AS T2 ON ( T1.Col1 = T2.Col1 )
JOIN Table3 as T3 ON T1.Col1 = T3.Col1
WHERE
( T1.Col2 = 'A' collate utf8_bin AND T1.col3 IN ( 'X' collate utf8_bin , 'X-Y' collate utf8_bin ) AND T1.Col1 != 1 )
and T2.static1=1
AND ACOS( ( 2.3 * T3.static2 + 1.2 * T3.static3 ) ) <= 0.00252321929476
ORDER BY T1.Col6
下面按照解释扩展
+----+-------------+-------+--------+-----------------------------------+-----------------+---------+----------------+------+----------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+--------+-----------------------------------+-----------------+---------+----------------+------+----------+-----------------------------+
| 1 | SIMPLE | T1 | ref | PRIMARY,col2,col3,CompositeIndex1 | CompositeIndex1 | 1 | const | 1 | 100.00 | Using where; Using filesort |
| 1 | SIMPLE | T2 | eq_ref | PRIMARY,CompositeIndex1 | PRIMARY | 4 | testdb.T1.col1 | 1 | 100.00 | Using where |
| 1 | SIMPLE | T3 | eq_ref | PRIMARY | PRIMARY | 4 | testdb.T1.col1 | 1 | 100.00 | Using where |
+----+-------------+-------+--------+-----------------------------------+-----------------+---------+----------------+------+----------+-----------------------------+
列的是否与相同:select_type,table,type,key,ref,filtered,Extra?
我的优化目标是:
-在少数索引中满足where条件
-避免计算
-避免整理转换
-避免或
-在where条件下避免NULL
现在的坏消息
看来在表中您已经有约14万条记录,并且如果查询涉及很多行,则使用顺序查询可能意味着使用文件排序方法,因此最终答案可以按照@mavroprovato的建议增加内存排序缓冲区。
进一步添加3
要评估 key_buffer_size 是否足够,请参阅http://dba.stackexchange.com
进一步添加4
我认为只有Oracle中的某人可以确切地说出发生了什么,但是我有主意。
我认为这个查询很奇怪:
因为有1个from_table_rows> = join1_table_rows> = join2_table_rows,所以最快从from表返回的行将是另外2个JOIN
用于评估工作量的优化器将计算出一个类似的公式:
effort = num_rows*key_size/index_cardinality
(index_cardinality由phpmyadmin显示在每个索引旁边)
由于2的努力是> = num_rows
我的查询
由于3,table1(来自table)返回92333行,table3(join1_table)减少到1(!)行,table2保留1行(努力〜3)。
您的查询
由于2,您应该付出的努力= 140000,但幸运的是calc仅返回1个结果,因此您的查询非常快捷
演示
在您的查询从“<= 10”(在联接条件下)更改为“<= 1000”或更多时,您将看到性能呈指数下降。
在我的查询中,从“<= 10”(在连接条件下)更改为“<= 1000”或更多,您将看到性能的线性/对数下降。
进一步添加5
问题的答案:排序缓冲区大小是否太大?
站在文章上,是的,请尝试一些调整,也许您可以解决问题
问题的答案:不可能快速查询吗?
恕我直言,这是可能的(即使sort-buffer-size无法解析)。
我的想法很简单,可以在这种情况下继续进行:“圆弧不错,但方形更好。”
目前,最大基数在表3中的坐标上,但是由于公式原因,索引不适用。因此,您可以搜索“正方形”内的所有点,而不是搜索半径内的所有点
FROM table3
...
WHERE (t3.latitude-0.15) < $usersLatitude AND $usersLatitude < t3.latitude+0.15
AND t3.longitue - 0.15 < $usersLongitude AND $usersLongitude < t3.longitue + 0.15
因此您可以在(t3.latitude,t3.longitue)中创建索引。
0.15度应为10英里。
当然,您应该在日变化子午线附近和两极附近修复计算
如果您严格需要半径,则可以使用半径公式(请参见下面的示例)或重新连接table3(如果可能)执行(/详细说明)公式,直到可以直接将值与列进行比较。
FROM table3 t3
JOIN table3 t3bis ON t3.id=t3bis.id
...
WHERE (t3.latitude-0.15) < $usersLatitude AND $usersLatitude < t3.latitude+0.15
AND t3.longitue - 0.15 < $usersLongitude AND $usersLongitude < t3.longitue + 0.15
AND
3963.191
* ACOS( (SIN(PI() * $usersLatitude / 180) * SIN(PI() * t3bis.latitude / 180))
+ ( COS(PI() * $usersLatitude / 180) * COS(PI() * t3bis.latitude / 180)
* COS(PI() * t3bis.longitude / 180 - PI() * 37.1092162 / 180)
)
) <= 10
再添加6
主题:编译函数做得更好
使用RADIANS()函数
degree * PI / 180 == radians(degree)
使用mysql的GIS扩展
看到这个article about MySql GIS extension
关于mysql - 如何使JOIN查询中的ORDER BY更快?我没有尝试过,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13569769/