问题 准备好的陈述是否是正常查询的浪费? (PHP)
如今,“准备好的语句”似乎是任何人建议向数据库发送查询的唯一方式。我甚至看到了为存储过程使用预准备语句的建议。但是,对于额外的查询准备语句需要 - 以及它们持续的短时间 - 我被说服它们仅对一行INSERT / UPDATE查询有用。
我希望有人可以纠正我,但这似乎是整个“桌子是邪恶的”CSS重复的事情。如果用于布局,表格只是邪恶的 - 而不是表格数据。将DIV用于表格数据是违反WC3的风格。
同样明智的,纯SQL(或从AR生成的)似乎对80%的查询更有用,在大多数站点上都是单个SELECT,不再重复页面加载(我说的是脚本语言)像这里的PHP)。为什么我会让我过度征税的数据库准备一个声明,它只是在删除之前运行一次?
MySQL的:
准备好的声明是特定的
它创建的会话。
如果你没有终止会话
解除分配先前准备的
声明,服务器解除分配它
自动。
因此,在脚本结束时,PHP将自动关闭连接,您将丢失准备好的语句,只是为了让您的脚本在下次加载时重新创建它。
我错过了什么或者这只是一种降低性能的方法吗?
:更新时间:
我突然意识到我正在为每个脚本假设新的连接。我认为如果使用持久连接,那么这些问题就会消失。它是否正确?
:UPDATE2:
似乎即使持久连接是解决方案 - 它们也是 不是一个很好的选择 对于大多数网络 - 特别是如果您使用交易。所以我回到原点只有下面的基准测试继续......
:UPDATE3:
大多数人只是重复“准备语句防止SQL注入”这一短语,但并未完全解释问题。为每个DB库提供的“转义”方法 还可以防止SQL注入。但它不止于此:
以正常方式发送查询时,
客户端(脚本) 转换数据
成为字符串 然后传递给
数据库服务器。然后DB服务器使用
CPU供电 将它们转换回来 成
正确的二进制数据类型。该
数据库引擎然后解析
语句并查找语法错误。
当使用准备好的陈述时......
数据以原生二进制形式发送,
这节省了转换CPU使用率,
并使数据传输更多
高效。显然,这也将
如果客户端减少带宽使用
与DB服务器不在同一位置。
...变量类型是预定义的,
因此MySQL考虑到了
这些人物,他们不需要
被逃脱。
http://www.webdesignforums.net/showthread.php?t=18762
感谢OIS最终让我对这个问题感到不满。
8199
2017-12-04 21:37
起源
答案:
与CSS表辩论不同,准备好的陈述有明显的安全隐患。
如果您使用预准备语句作为将用户提供的数据放入查询的唯一方法,那么它们在SQL注入时绝对是防弹的。
6
2017-12-04 21:44
答案:
与CSS表辩论不同,准备好的陈述有明显的安全隐患。
如果您使用预准备语句作为将用户提供的数据放入查询的唯一方法,那么它们在SQL注入时绝对是防弹的。
6
2017-12-04 21:44
当您在数据库上执行sql语句时,sql解析器需要事先对其进行分析,这与准备工作完全相同。
因此,将执行sql语句直接与准备和执行进行比较没有任何缺点,但有一些优点:
$sql = 'SELECT u.id, u.user, u.email, sum(r.points)
FROM users u
LEFT JOIN reputation r on (u.id=r.user_id)
LEFT JOIN badge b on (u.id=b.user_id and badge=:badge)
WHERE group=:group';
$params = array(
':group' => $group,
':badge' => $_GET['badge']
);
$stmt = $pdo->prepare($sql);
$result = $stmt->execute($params);
代替
$sql = 'SELECT u.id, u.user, u.email, sum(r.points)
FROM users u
LEFT JOIN reputation r on (u.id=r.user_id)
LEFT JOIN badge b on (u.id=b.user_id and badge="'.mysql_real_escape_string($_GET['badge']).'")
WHERE group="'.mysql_real_escape_string($group).'"';
$result = mysql_query($sql);
想象一下你必须改变sql语句,哪个代码是你最喜欢的? ;-)
1
2017-12-04 21:59
准备好的声明在几种情况下派上用场:
- 查询数据与不受信任的用户数据的完美分离。
- 多次执行相同查询时性能会提高
- 传输二进制数据时性能会提高,因为预准备语句可以使用二进制协议,而传统查询最终会进行编码等。
在正常情况下(不重复,没有二进制数据)会有性能损失,因为您现在必须来回两次。第一个“准备”查询,第二个是将令牌与要插入的数据一起发送。大多数人都愿意为安全利益做出这种牺牲。
关于持久连接:
MySQL拥有市场上最快的连接建立时间之一。它对于大多数设置来说基本上是免费的,所以你不会使用持久连接看到太多的变化。
1
2017-12-04 23:49
答案与安全性和抽象性有关。其他人都已经提到了安全性,但真正的好处是你的输入完全是从查询本身中抽象出来的。这允许在使用抽象层时具有真正的数据库不可知性,而内联输入通常是依赖于数据库的过程。如果你关心任何可移植性,准备好的陈述是要走的路。
在现实世界中,我很少编写DML查询。我的所有INSERTS / UPDATES都是由抽象层自动构建的,只需传递一个输入数组即可执行。对于所有意图和目的,准备查询然后执行它们确实没有“性能损失”(保存初始PREPARE中的连接延迟)。但是当使用UDS(Unix域套接字)连接时,您不会注意到(甚至能够进行基准测试)差异。它通常在几微秒的量级。
考虑到安全性和抽象优势,我几乎不称它为浪费。
1
2017-12-05 00:13
性能优势并非来自较少的解析 - 它来自于只需要一次而不是重复地计算访问路径。当您发出数千个查询时,这会有很大帮助。
鉴于mysql非常简单的优化器/规划器,这可能不像使用更复杂的优化器的更成熟的数据库。
但是,如果您有一个了解数据偏差的复杂优化器,那么这种性能优势实际上会变得有害。在这种情况下,通常可以更好地使用不同的文字值为同一查询获取不同的访问路径,而不是重用预先存在的路径。
1
2017-12-05 05:19
当使用sql查询时 SELECT x,y,z FROM foo WHERE c='mary had a little lamb'
服务器必须解析包含数据的sql语句+你必须清理“mary have ...”部分(对每个参数调用mysql_real_escape()或类似)。
使用预准备语句,服务器也必须解析语句,但没有数据,只返回语句的标识符(一个微小的数据包)。然后发送实际数据,而不先清理它。我没有看到这里的开销,但我承认我从未测试过它。你呢? ;-)
编辑:使用预处理语句可以消除将每个参数(输入/输出)转换为字符串的需要。如果您的php版本使用,可能更是如此 mysqlnd (而不是“旧的”libmysql客户端库)。还没有测试过那个性能方面。
0
2017-12-04 21:58
我似乎没有找到使用持久连接的任何好处 - 或为该主管准备好的语句。看看这些数字 - 对于6000个选择语句(在页面请求中永远不会发生!),你几乎无法区分它们。我的大多数页面使用的查询少于10个。
更新 我刚把我的测试修改为
包括4k SELECT和4k INSERT
声明!自己动手让我
知道是否有任何设计错误。
如果我的MySQL服务器没有在与Apache相同的机器上运行,那么差异可能更大。
Persistent: TRUE
Prepare: TRUE
2.3399310112 seconds
Persistent: FALSE
Prepare: TRUE
2.3265211582184 seconds
Persistent: TRUE
Prepare: FALSE
2.3666892051697 seconds
Persistent: FALSE
Prepare: FALSE
2.3496441841125 seconds
这是我的测试代码:
$hostname = 'localhost';
$username = 'root';
$password = '';
$dbname = 'db_name';
$persistent = FALSE;
$prepare = FALSE;
try
{
// Force PDO to use exceptions for all errors
$attrs = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION);
if($persistent)
{
// Make the connection persistent
$attrs[PDO::ATTR_PERSISTENT] = TRUE;
}
$db = new PDO("mysql:host=$hostname;dbname=$dbname", $username, $password, $attrs);
// What type of connection?
print 'Persistent: '.($db->getAttribute(PDO::ATTR_PERSISTENT) ? 'TRUE' : 'FALSE').'<br />';
print 'Prepare: '.($prepare ? 'TRUE' : 'FALSE').'<br />';
//Clean table from last run
$db->exec('TRUNCATE TABLE `pdo_insert`');
}
catch(PDOException $e)
{
echo $e->getMessage();
}
$start = microtime(TRUE);
$name = 'Jack';
$body = 'This is the text "body"';
if( $prepare ) {
// Select
$select = $db->prepare('SELECT * FROM pdo_insert WHERE id = :id');
$select->bindParam(':id', $x);
// Insert
$insert = $db->prepare('INSERT INTO pdo_insert (`name`, `body`, `author_id`)
VALUES (:name, :body, :author_id)');
$insert->bindParam(':name', $name);
$insert->bindParam(':body', $body);
$insert->bindParam(':author_id', $x);
$run = 0;
for($x=0;$x<4000;++$x)
{
if( $insert->execute() && $select->execute() )
{
$run++;
}
}
}
else
{
$run = 0;
for($x=0;$x<4000;++$x) {
// Insert
if( $db->query('INSERT INTO pdo_insert (`name`, `body`, `author_id`)
VALUES ('.$db->quote($name).', '. $db->quote($body).', '. $db->quote($x).')')
AND
// Select
$db->query('SELECT * FROM pdo_insert WHERE id = '. $db->quote($x)) )
{
$run++;
}
}
}
print (microtime(true) - $start).' seconds and '.($run * 2).' queries';
0
2017-12-04 22:42
卡西是对的。如果您不准备/编译它,dbms必须在任何情况下才能运行它。
此外,优点是您可以检查准备结果,如果准备失败,您的算法可以分支处理异常而不浪费数据库资源来运行失败的查询。
0
2017-12-05 00:10