MySQL 和 PDO,加速查询并从 MySQL 函数(例程)中获取 result/output?
MySQL and PDO, speed up query and get result/output from MySQL function (routine)?
获取值:
我从 here 得到 levenshtein_ratio 函数,在我的 MySQL 数据库中排队。我运行它是用下面的方式:
$stmt = $db->prepare("SELECT r_id, val FROM table WHERE levenshtein_ratio(:input, someval) > 70");
$stmt->execute(array('input' => $input));
$result = $stmt->fetchAll();
if(count($result)) {
foreach($result as $row) {
$out .= $row['r_id'] . ', ' . $row['val'];
}
}
而且效果很好,完全符合预期。但我想知道,是否有一种很好的方法来获得 levenshtein_ratio()
计算的值?
我试过:
$stmt = $db->prepare("SELECT levenshtein_ratio(:input, someval), r_id, val FROM table WHERE levenshtein_ratio(:input, someval) > 70");
$stmt->execute(array('input' => $input));
$result = $stmt->fetchAll();
if(count($result)) {
foreach($result as $row) {
$out .= $row['r_id'] . ', ' . $row['val'] . ', ' . $row[0];
}
}
它 确实 技术上可行(我从 $row[0]
得到百分比),但查询有点难看,我不能使用适当的获取价值的关键,就像我可以为其他两项一样。
有没有办法以某种方式为它找到一个很好的参考?
我试过了:
$stmt = $db->prepare("SELECT r_id, val SET output=levenshtein_ratio(:input, someval) FROM table WHERE levenshtein_ratio(:input, someval) > 70");
根据我在网上找到的东西对其进行建模,但它没有用,最终破坏了整个查询。
加速:
我正在运行查询一组值:
foreach($parent as $input){
$stmt = ...
$stmt->execute...
$result = $stmt->fetchAll();
... etc
}
但最终速度非常慢。像 20s 慢,对于只有 14 个输入的数组和一个大约 350 行的数据库,预计很快就会达到 10,000 行。我知道将查询放在循环中是一件很麻烦的事,但我不确定还有什么办法可以解决这个问题。
编辑 1
当我使用
$stmt = $db->prepare("SELECT r_id, val SET output=levenshtein_ratio(:input, someval) FROM table WHERE levenshtein_ratio(:input, someval) > 70");
这肯定比我只计算一次要花费两倍的时间?类似于在 for 循环中使用 $i < sizeof($arr);
?
要清理列名,您可以使用 "as" 重命名函数的列。同时,您可以通过在 where 子句中使用该列名称来加快速度,这样该函数只执行一次。
$stmt = $db->prepare("SELECT r_id, levenshtein_ratio(:input, someval) AS val FROM table HAVING val > 70");
如果它仍然太慢,你可以考虑像 https://github.com/juanmirocks/Levenshtein-MySQL-UDF
这样的 c 库
doh - 忘记将 "where" 切换为 "having",如 spencer7593 所述。
我假设 `someval` 是对 table 中列的非限定引用。虽然您可能明白,如果不查看 table 定义,阅读 SQL 语句的其他人无法分辨。作为对未来读者的帮助,请考虑 限定 您的列引用,使用 table 的名称或(最好)在语句中分配给 table 的短别名.
SELECT t.r_id
, t.val
FROM `table` t
WHERE levenshtein_ratio(:input, t.someval) > 70
必须针对 table 中的 每个 行评估 WHERE 子句中的该函数。没有办法让 MySQL 建立索引。所以没有办法让 MySQL 执行索引范围扫描操作。
可能 MySQL 为查询使用索引,例如,如果查询有一个 ORDER BY t.val
子句,或者如果有一个 "covering index" 可用.
但这并不能解决需要为每一行计算函数的问题。 (如果查询有其他排除行的谓词,则不一定需要为排除的行评估该函数。)
如果函数被声明为 DETERMINISTIC,则将表达式添加到 SELECT 列表确实不会太昂贵。第二次调用具有相同参数的 DETERMINISTIC 函数可以重用上一次执行的值 returned。 (声明一个函数 DETERMINISTIC 本质上意味着当给定相同的参数值时,该函数保证 return 相同的结果。重复调用将 return 相同的值。也就是说, return 值只依赖于参数值,不依赖于任何其他东西。
SELECT t.r_id
, t.val
, levenshtein_ratio(:input, t.someval) AS lev_ratio
FROM `table` t
WHERE levenshtein_ratio(:input2, t.someval) > 70
(注意:我为第二个引用使用了不同的绑定占位符名称,因为 PDO 没有像我们预期的那样处理 "duplicate" 绑定占位符名称。(这可能在最近的版本中得到了纠正PDO 的版本。该问题的第一个 "fix" 是对文档的更新,指出绑定占位符名称应该只在语句中出现一次,如果您需要对同一值的两个引用,请使用两个不同的占位符名称并绑定两者的价值相同。)
如果不想重复表达式,可以将条件从 WHERE 子句移动到 HAVING,并通过分配给列的别名引用 SELECT 列表中的表达式。
SELECT t.r_id
, t.val
, levenshtein_ratio(:input, t.someval) AS lev_ratio
FROM `table` t
HAVING lev_ratio > 70
WHERE 和 HAVING 的最大区别在于 WHERE 子句中的谓词是在访问行时计算的。在访问行之后,HAVING 子句的计算时间要晚得多。 (这简要解释了为什么 HAVING 子句可以通过别名引用 SELECT 列表中的列,但 WHERE 子句不能这样做。)
如果这是一个大 table,并且排除了大量行,则使用 HAVING 子句可能会有显着的性能差异。可能会创建更大的中间集。
要获得 "index used" 查询,覆盖索引是我看到的唯一选项。
ON `table` (r_id, val, someval)
这样,MySQL 就可以满足索引的查询,而不需要在底层 table 中查找页面。查询需要的所有列值都可以从索引中获得。
跟进
要创建索引,我们需要创建一个列,例如
lev_ratio_foo FLOAT
并预填充函数
的结果
UPDATE `table` t
SET t.lev_ratio_foo = levenshtein_ratio('foo', t.someval)
;
然后我们可以创建一个索引,例如
... ON `table` (lev_ratio_foo, val, r_id)
并重新编写查询
SELECT t.r_id
, t.val
, t.lev_ratio_foo
FROM `table` t
WHERE t.lev_ratio_foo > 70
通过该查询,MySQL 可以对以 lev_ratio_foo 作为前导列的索引使用索引范围扫描操作。
我们可能希望添加 BEFORE INSERT 和 BEFORE UPDATE 触发器来维护值,当新行添加到 table 或修改某些列的值时。
可以扩展该模式,可以为 'foo' 以外的值添加额外的列。例如'bar'
UPDATE `table` t
SET t.lev_ratio_bar = levenshtein_ratio('bar', t.someval)
显然,该方法无法针对广泛的输入值进行扩展。
获取值:
我从 here 得到 levenshtein_ratio 函数,在我的 MySQL 数据库中排队。我运行它是用下面的方式:
$stmt = $db->prepare("SELECT r_id, val FROM table WHERE levenshtein_ratio(:input, someval) > 70");
$stmt->execute(array('input' => $input));
$result = $stmt->fetchAll();
if(count($result)) {
foreach($result as $row) {
$out .= $row['r_id'] . ', ' . $row['val'];
}
}
而且效果很好,完全符合预期。但我想知道,是否有一种很好的方法来获得 levenshtein_ratio()
计算的值?
我试过:
$stmt = $db->prepare("SELECT levenshtein_ratio(:input, someval), r_id, val FROM table WHERE levenshtein_ratio(:input, someval) > 70");
$stmt->execute(array('input' => $input));
$result = $stmt->fetchAll();
if(count($result)) {
foreach($result as $row) {
$out .= $row['r_id'] . ', ' . $row['val'] . ', ' . $row[0];
}
}
它 确实 技术上可行(我从 $row[0]
得到百分比),但查询有点难看,我不能使用适当的获取价值的关键,就像我可以为其他两项一样。
有没有办法以某种方式为它找到一个很好的参考?
我试过了:
$stmt = $db->prepare("SELECT r_id, val SET output=levenshtein_ratio(:input, someval) FROM table WHERE levenshtein_ratio(:input, someval) > 70");
根据我在网上找到的东西对其进行建模,但它没有用,最终破坏了整个查询。
加速:
我正在运行查询一组值:
foreach($parent as $input){
$stmt = ...
$stmt->execute...
$result = $stmt->fetchAll();
... etc
}
但最终速度非常慢。像 20s 慢,对于只有 14 个输入的数组和一个大约 350 行的数据库,预计很快就会达到 10,000 行。我知道将查询放在循环中是一件很麻烦的事,但我不确定还有什么办法可以解决这个问题。
编辑 1
当我使用
$stmt = $db->prepare("SELECT r_id, val SET output=levenshtein_ratio(:input, someval) FROM table WHERE levenshtein_ratio(:input, someval) > 70");
这肯定比我只计算一次要花费两倍的时间?类似于在 for 循环中使用 $i < sizeof($arr);
?
要清理列名,您可以使用 "as" 重命名函数的列。同时,您可以通过在 where 子句中使用该列名称来加快速度,这样该函数只执行一次。
$stmt = $db->prepare("SELECT r_id, levenshtein_ratio(:input, someval) AS val FROM table HAVING val > 70");
如果它仍然太慢,你可以考虑像 https://github.com/juanmirocks/Levenshtein-MySQL-UDF
这样的 c 库doh - 忘记将 "where" 切换为 "having",如 spencer7593 所述。
我假设 `someval` 是对 table 中列的非限定引用。虽然您可能明白,如果不查看 table 定义,阅读 SQL 语句的其他人无法分辨。作为对未来读者的帮助,请考虑 限定 您的列引用,使用 table 的名称或(最好)在语句中分配给 table 的短别名.
SELECT t.r_id
, t.val
FROM `table` t
WHERE levenshtein_ratio(:input, t.someval) > 70
必须针对 table 中的 每个 行评估 WHERE 子句中的该函数。没有办法让 MySQL 建立索引。所以没有办法让 MySQL 执行索引范围扫描操作。
可能 MySQL 为查询使用索引,例如,如果查询有一个 ORDER BY t.val
子句,或者如果有一个 "covering index" 可用.
但这并不能解决需要为每一行计算函数的问题。 (如果查询有其他排除行的谓词,则不一定需要为排除的行评估该函数。)
如果函数被声明为 DETERMINISTIC,则将表达式添加到 SELECT 列表确实不会太昂贵。第二次调用具有相同参数的 DETERMINISTIC 函数可以重用上一次执行的值 returned。 (声明一个函数 DETERMINISTIC 本质上意味着当给定相同的参数值时,该函数保证 return 相同的结果。重复调用将 return 相同的值。也就是说, return 值只依赖于参数值,不依赖于任何其他东西。
SELECT t.r_id
, t.val
, levenshtein_ratio(:input, t.someval) AS lev_ratio
FROM `table` t
WHERE levenshtein_ratio(:input2, t.someval) > 70
(注意:我为第二个引用使用了不同的绑定占位符名称,因为 PDO 没有像我们预期的那样处理 "duplicate" 绑定占位符名称。(这可能在最近的版本中得到了纠正PDO 的版本。该问题的第一个 "fix" 是对文档的更新,指出绑定占位符名称应该只在语句中出现一次,如果您需要对同一值的两个引用,请使用两个不同的占位符名称并绑定两者的价值相同。)
如果不想重复表达式,可以将条件从 WHERE 子句移动到 HAVING,并通过分配给列的别名引用 SELECT 列表中的表达式。
SELECT t.r_id
, t.val
, levenshtein_ratio(:input, t.someval) AS lev_ratio
FROM `table` t
HAVING lev_ratio > 70
WHERE 和 HAVING 的最大区别在于 WHERE 子句中的谓词是在访问行时计算的。在访问行之后,HAVING 子句的计算时间要晚得多。 (这简要解释了为什么 HAVING 子句可以通过别名引用 SELECT 列表中的列,但 WHERE 子句不能这样做。)
如果这是一个大 table,并且排除了大量行,则使用 HAVING 子句可能会有显着的性能差异。可能会创建更大的中间集。
要获得 "index used" 查询,覆盖索引是我看到的唯一选项。
ON `table` (r_id, val, someval)
这样,MySQL 就可以满足索引的查询,而不需要在底层 table 中查找页面。查询需要的所有列值都可以从索引中获得。
跟进
要创建索引,我们需要创建一个列,例如
lev_ratio_foo FLOAT
并预填充函数
的结果UPDATE `table` t
SET t.lev_ratio_foo = levenshtein_ratio('foo', t.someval)
;
然后我们可以创建一个索引,例如
... ON `table` (lev_ratio_foo, val, r_id)
并重新编写查询
SELECT t.r_id
, t.val
, t.lev_ratio_foo
FROM `table` t
WHERE t.lev_ratio_foo > 70
通过该查询,MySQL 可以对以 lev_ratio_foo 作为前导列的索引使用索引范围扫描操作。
我们可能希望添加 BEFORE INSERT 和 BEFORE UPDATE 触发器来维护值,当新行添加到 table 或修改某些列的值时。
可以扩展该模式,可以为 'foo' 以外的值添加额外的列。例如'bar'
UPDATE `table` t
SET t.lev_ratio_bar = levenshtein_ratio('bar', t.someval)
显然,该方法无法针对广泛的输入值进行扩展。