Mysql 服务器在特定定位查询时超时
Mysql Server timing out on specific locate queries
我正在使用 ZF3 和 DB 模块编写搜索程序。
每次我使用超过 1 个短关键字时 - 如“49”和 "am" 或“1”和 "is" 我得到这个错误:
Statement could not be executed (HY000 - 2006 - MySQL server has gone away)
只要我不使用 2 个或更多的短关键字,使用较长的关键字效果很好。
该问题仅发生在实时服务器上,它在本地测试服务器上工作正常。
项目 table 有 ~2200 行各种数据 project_search table 有 17000 行,每个项目有多个条目,每个看起来像:
id, projectid, searchtext
searchtext 列是全文。这里是 php 代码的相关部分:
$sql = new Sql($this->db);
$select = $sql->select(['p'=>'projects']);
if(isset($filter['search'])) {
$keywords = preg_split('/\s+/', trim($filter['search']));
$join = $sql->select('project_search');
$join->columns(['projectid' => new Expression('DISTINCT(projectid)')]);
$join->group("projectid");
foreach($keywords as $keyword) {
$join->having(["LOCATE('$keyword', GROUP_CONCAT(searchtext))"]);
}
$select->join(
["m" => $join],
"m.projectid = p.id",
['projectid'],
\Zend\Db\Sql\Select::JOIN_RIGHT
);
}
这里是结果查询:
SELECT p.*, m.projectid
FROM projects AS p
INNER JOIN (
SELECT projectid
FROM project_search
GROUP BY projectid
HAVING LOCATE('am', GROUP_CONCAT(searchtext))
AND LOCATE('49', GROUP_CONCAT(searchtext))
) AS m
ON m.projectid = p.id
GROUP BY p.id
ORDER BY createdAt DESC
我使用 "MATCH(searchtext) AGAINST('$keyword)" 和“searchtext LIKE '%keyword%' 重写了查询,结果相同。
问题似乎出在实时 mysql 服务器上,我该如何调试它?
[编辑]
在注意到错误仅发生在具有其他搜索相关查询的特殊视图中之后——每个查询都使用多个连接(1 个连接/关键字)——我合并了这些查询,错误消失了。查询量似乎要杀死服务器。
尝试像这样重构您的内部查询。
SELECT a.projectid
FROM (
SELECT DISTINCT projectid
FROM projectsearch
WHERE searchtext LIKE '%am%'
) a
JOIN (
SELECT DISTINCT projectid
FROM projectsearch
WHERE searchtext LIKE '%49%'
) b ON a.projectid = b.projectid
它应该返回与您的内部查询相同的一组 projectid
值。它会为两个搜索词提供匹配 searchtext
的每个 projectid
值,即使这些词出现在 project_search
的不同行中也是如此。这就是您的查询通过搜索 GROUP_CONCAT()
输出所做的。
尝试在 (searchtext, projectid)
上创建索引。使用 column LIKE '%sample'
意味着您将无法随机访问该索引,但连接中的两个查询可能仍然能够扫描索引,这比扫描 table 更快。要添加该索引,请使用此命令。
ALTER TABLE project_search ADD INDEX project_search_text (searchtext, projectid);
尝试在 MySQL 客户端程序(例如 phpmyadmin)中执行此操作,而不是直接从您的 php 程序执行此操作。
然后,使用 MySQL 客户端测试内部查询。看看需要多长时间。使用 EXPLAIN SELECT ....
来了解 MySQL 如何处理查询。
您的短关键字可能会返回多得离谱的匹配项,并以某种方式使您的系统不堪重负。在那种情况下,您可以在内部查询的末尾放置一个 LIMIT 1000
子句或类似的东西。不过,这不太可能。 17千克不是一个大数字。
如果这对您的生产没有帮助 MySQL 服务器可能配置错误或损坏。如果我是你,我会打电话给你的托管服务技术支持,以某种方式通过前线支持代理(除了 "reboot your computer" 和其他类似的愚蠢之外什么都不知道),并告诉他们你得到的确切时间"gone away" 消息。他们将能够检查日志。
专业提示:我相信您知道使用 LIKE '%text%'
作为搜索词的陷阱。它不可扩展,因为它不是 sargable:它不能随机访问索引。如果您可以重新设计您的系统,那么您的时间和精力是值得的。
您可以 TRY / CATCH 来检查您是否遇到更具体的错误:
BEGIN TRY
BEGIN TRANSACTION
--Insert Your Queries Here--
COMMIT
END TRY
BEGIN CATCH
DECLARE @ErrorMessage NVARCHAR(4000);
DECLARE @ErrorSeverity INT;
DECLARE @ErrorState INT;
SELECT
@ErrorMessage = ERROR_MESSAGE(),
@ErrorSeverity = ERROR_SEVERITY(),
@ErrorState = ERROR_STATE();
IF @@TRANCOUNT > 0
ROLLBACK
RAISERROR (@ErrorMessage, -- Message text.
@ErrorSeverity, -- Severity.
@ErrorState -- State.
);
END CATCH
虽然因为你在谈论短词和全文,但在我看来它必须与 StopWords 有关。
从您的开发服务器和生产服务器尝试运行此查询并检查是否存在任何差异:
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD;
同时检查 my.ini(如果这是配置文件)文本文件,如果这些设置为:
ft_stopword_file = ""
ft_min_word_len = 1
如我的编辑所述,问题不是来自原始问题的查询,而是其他一些使用搜索参数的查询。每个查询都有如下部分:
if(isset($filter['search'])) {
$keywords = preg_split('/\s+/', trim($filter['search']));
$field = 1;
foreach($keywords as $keyword) {
$join = $sql->select('project_search');
$join->columns(["pid$field" => 'projectid']);
$join->where(["LOCATE('$keyword', searchtext)"]);
$join->group("projectid");
$select->join(
["m$field" => $join],
"m$field.pid$field = p.id"
);
$field++;
}
}
这导致大量查询和大量结果行最终杀死了 mysql 服务器。我将这些查询合并到第一个中,错误消失了。
我正在使用 ZF3 和 DB 模块编写搜索程序。 每次我使用超过 1 个短关键字时 - 如“49”和 "am" 或“1”和 "is" 我得到这个错误:
Statement could not be executed (HY000 - 2006 - MySQL server has gone away)
只要我不使用 2 个或更多的短关键字,使用较长的关键字效果很好。 该问题仅发生在实时服务器上,它在本地测试服务器上工作正常。
项目 table 有 ~2200 行各种数据 project_search table 有 17000 行,每个项目有多个条目,每个看起来像:
id, projectid, searchtext
searchtext 列是全文。这里是 php 代码的相关部分:
$sql = new Sql($this->db);
$select = $sql->select(['p'=>'projects']);
if(isset($filter['search'])) {
$keywords = preg_split('/\s+/', trim($filter['search']));
$join = $sql->select('project_search');
$join->columns(['projectid' => new Expression('DISTINCT(projectid)')]);
$join->group("projectid");
foreach($keywords as $keyword) {
$join->having(["LOCATE('$keyword', GROUP_CONCAT(searchtext))"]);
}
$select->join(
["m" => $join],
"m.projectid = p.id",
['projectid'],
\Zend\Db\Sql\Select::JOIN_RIGHT
);
}
这里是结果查询:
SELECT p.*, m.projectid
FROM projects AS p
INNER JOIN (
SELECT projectid
FROM project_search
GROUP BY projectid
HAVING LOCATE('am', GROUP_CONCAT(searchtext))
AND LOCATE('49', GROUP_CONCAT(searchtext))
) AS m
ON m.projectid = p.id
GROUP BY p.id
ORDER BY createdAt DESC
我使用 "MATCH(searchtext) AGAINST('$keyword)" 和“searchtext LIKE '%keyword%' 重写了查询,结果相同。
问题似乎出在实时 mysql 服务器上,我该如何调试它?
[编辑]
在注意到错误仅发生在具有其他搜索相关查询的特殊视图中之后——每个查询都使用多个连接(1 个连接/关键字)——我合并了这些查询,错误消失了。查询量似乎要杀死服务器。
尝试像这样重构您的内部查询。
SELECT a.projectid
FROM (
SELECT DISTINCT projectid
FROM projectsearch
WHERE searchtext LIKE '%am%'
) a
JOIN (
SELECT DISTINCT projectid
FROM projectsearch
WHERE searchtext LIKE '%49%'
) b ON a.projectid = b.projectid
它应该返回与您的内部查询相同的一组 projectid
值。它会为两个搜索词提供匹配 searchtext
的每个 projectid
值,即使这些词出现在 project_search
的不同行中也是如此。这就是您的查询通过搜索 GROUP_CONCAT()
输出所做的。
尝试在 (searchtext, projectid)
上创建索引。使用 column LIKE '%sample'
意味着您将无法随机访问该索引,但连接中的两个查询可能仍然能够扫描索引,这比扫描 table 更快。要添加该索引,请使用此命令。
ALTER TABLE project_search ADD INDEX project_search_text (searchtext, projectid);
尝试在 MySQL 客户端程序(例如 phpmyadmin)中执行此操作,而不是直接从您的 php 程序执行此操作。
然后,使用 MySQL 客户端测试内部查询。看看需要多长时间。使用 EXPLAIN SELECT ....
来了解 MySQL 如何处理查询。
您的短关键字可能会返回多得离谱的匹配项,并以某种方式使您的系统不堪重负。在那种情况下,您可以在内部查询的末尾放置一个 LIMIT 1000
子句或类似的东西。不过,这不太可能。 17千克不是一个大数字。
如果这对您的生产没有帮助 MySQL 服务器可能配置错误或损坏。如果我是你,我会打电话给你的托管服务技术支持,以某种方式通过前线支持代理(除了 "reboot your computer" 和其他类似的愚蠢之外什么都不知道),并告诉他们你得到的确切时间"gone away" 消息。他们将能够检查日志。
专业提示:我相信您知道使用 LIKE '%text%'
作为搜索词的陷阱。它不可扩展,因为它不是 sargable:它不能随机访问索引。如果您可以重新设计您的系统,那么您的时间和精力是值得的。
您可以 TRY / CATCH 来检查您是否遇到更具体的错误:
BEGIN TRY
BEGIN TRANSACTION
--Insert Your Queries Here--
COMMIT
END TRY
BEGIN CATCH
DECLARE @ErrorMessage NVARCHAR(4000);
DECLARE @ErrorSeverity INT;
DECLARE @ErrorState INT;
SELECT
@ErrorMessage = ERROR_MESSAGE(),
@ErrorSeverity = ERROR_SEVERITY(),
@ErrorState = ERROR_STATE();
IF @@TRANCOUNT > 0
ROLLBACK
RAISERROR (@ErrorMessage, -- Message text.
@ErrorSeverity, -- Severity.
@ErrorState -- State.
);
END CATCH
虽然因为你在谈论短词和全文,但在我看来它必须与 StopWords 有关。
从您的开发服务器和生产服务器尝试运行此查询并检查是否存在任何差异:
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD;
同时检查 my.ini(如果这是配置文件)文本文件,如果这些设置为:
ft_stopword_file = ""
ft_min_word_len = 1
如我的编辑所述,问题不是来自原始问题的查询,而是其他一些使用搜索参数的查询。每个查询都有如下部分:
if(isset($filter['search'])) {
$keywords = preg_split('/\s+/', trim($filter['search']));
$field = 1;
foreach($keywords as $keyword) {
$join = $sql->select('project_search');
$join->columns(["pid$field" => 'projectid']);
$join->where(["LOCATE('$keyword', searchtext)"]);
$join->group("projectid");
$select->join(
["m$field" => $join],
"m$field.pid$field = p.id"
);
$field++;
}
}
这导致大量查询和大量结果行最终杀死了 mysql 服务器。我将这些查询合并到第一个中,错误消失了。