如今,“准备好的语句”似乎是任何人建议向数据库发送查询的唯一方式。我甚至看到了为存储过程使用预准备语句的建议。但是,对于额外的查询准备语句需要 - 以及它们持续的短时间 - 我被说服它们仅对一行INSERT / UPDATE查询有用。
我希望有人可以在此纠正我,但这似乎是整个“桌子都是邪恶的”CSS事情的重复。如果用于布局,表格只是邪恶的 - 而不是表格数据。对表格数据使用DIV是违反WC3的风格。
同样明智的,SQL(或者从AR生成的)似乎对80%的查询更有用,在大多数站点上都是单个SELECT,不再重复那个页面加载(我说话)关于PHP这样的脚本语言)。为什么我要让我过度征税的数据库准备一个声明,它只能在删除之前运行一次?
MySQL的:
准备好的声明特定于 它创建的会话。 如果你没有终止会话 解除以前准备的 声明,服务器解除分配它 自动。
因此,在脚本结束时,PHP将自动关闭连接,您将丢失准备好的语句,只是为了让您的脚本在下次加载时重新创建它。
我错过了什么或者这只是一种降低性能的方法吗?
:UPDATE:
我突然意识到我正在为每个脚本假设新的连接。我认为如果使用持久连接,那么这些问题就会消失。这是对的吗?
:UPDATE2:
似乎即使持久连接是解决方案 - 对于大多数网络来说它们都是not a very good option - 特别是如果你使用交易。所以我回到原点只有下面的基准测试......
:UPDATE3:
大多数人只是重复短语“准备好的语句防止SQL注入”,但这并不能完全解释问题。为每个数据库库提供的“转义”方法也可以防止SQL注入。但它不止于此:
以正常方式发送查询时, 客户端(脚本)转换数据 到字符串然后传递给 数据库服务器。然后DB服务器使用 将CPU转换回来的电源 正确的二进制数据类型。该 然后数据库引擎解析 声明并查找语法错误。
使用准备好的陈述时...... 数据以原生二进制形式发送, 这节省了转换CPU使用率, 并使数据传输更多 高效。显然,这也将 如果客户端减少带宽使用 不与DB服务器位于同一位置。
...变量类型是预定义的, 因此MySQL考虑到了 这些人物,他们不需要 被逃脱。
感谢OIS最终解决了这个问题。
答案 0 :(得分:6)
与CSS表辩论不同,准备好的陈述有明显的安全隐患。
如果您使用预准备语句作为将用户提供的数据放入查询的唯一方法,那么它们在SQL注入时绝对是防弹的。
答案 1 :(得分:1)
当您在数据库上执行sql语句时,sql解析器需要事先对其进行分析,这与准备过程完全相同。
因此,将执行sql语句直接与准备和执行进行比较没有任何缺点,但有一些优点:
首先,正如longneck已经说过的那样,将用户输入传递到预准备语句会自动转义输入。就好像数据库已经为值准备了过滤器,并且只允许那些适合的值。
其次,如果彻底使用预处理语句,并且遇到需要多次执行它的情况,则不需要重写代码来准备和执行,但只需执行它。 / p>
第三:如果正确完成,代码将变得更具可读性:
$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语句,哪个代码是你最喜欢的? ; - )
答案 2 :(得分:1)
准备好的陈述在几种情况下派上用场:
在正常情况下(不重复,没有二进制数据)会导致性能下降,因为您现在必须来回两次。第一个用于“准备”查询,第二个用于传输令牌以及要插入的数据。大多数人都愿意为安全利益做出这种牺牲。
关于持久连接: MySQL拥有市场上最快的连接建立时间之一。它对于大多数设置来说基本上是免费的,所以你不会使用持久连接看到太多的变化。
答案 3 :(得分:1)
答案与安全性和抽象性有关。其他人都已经提到了安全性,但真正的好处是你的输入完全是从查询本身中抽象出来的。这允许在使用抽象层时实现真正的数据库不可知性,而内联输入通常是依赖于数据库的过程。如果你关心任何可移植性,准备好的陈述是要走的路。
在现实世界中,我很少编写DML查询。我的所有INSERTS / UPDATES都是由抽象层自动构建的,只需传递一个输入数组即可执行。对于所有意图和目的,准备查询然后执行它们确实没有“性能损失”(保存初始PREPARE中的连接延迟)。但是当使用UDS(Unix域套接字)连接时,您不会注意到(甚至能够进行基准测试)差异。它通常只有几微秒。
鉴于安全性和抽象性上升,我几乎不称它为浪费。
答案 4 :(得分:1)
性能优势并非来自较少的解析 - 它来自于只需要一次而不是重复地计算访问路径。当您发出数千个查询时,这会有很大帮助。
鉴于mysql的非常简单的优化器/规划器,这可能不是一个问题,而是一个更成熟的数据库和更复杂的优化器。
但是,如果您有一个了解数据偏差的复杂优化器,那么这种性能优势实际上会变得有害。在这种情况下,通常可以更好地使用不同的文字值为同一查询获取不同的访问路径,而不是重用预先存在的路径。
答案 5 :(得分:0)
当使用像SELECT x,y,z FROM foo WHERE c='mary had a little lamb'
这样的sql查询时,服务器必须解析包含数据的sql语句+你必须清理“mary have ...”部分(对每个参数调用mysql_real_escape()或类似)。
使用预准备语句,服务器也必须解析语句,但没有数据,只返回语句的标识符(一个微小的数据包)。然后您发送实际数据而不先清理它。我没有看到这里的开销,但我承认我从未测试过它。你呢? ;-)
编辑:使用预处理语句可以消除将每个参数(输入/输出)转换为字符串的需要。如果你的php版本使用mysqlnd(而不是“旧的”libmysql客户端库),可能更是如此。还没有测试过它的性能方面。
答案 6 :(得分:0)
我似乎没有找到使用持久连接的任何好处 - 或者为该主管准备好的语句。看看这些数字 - 对于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';
答案 7 :(得分:0)
卡西是对的。如果你没有准备/编译它,那么dbms在任何情况下都必须能够运行它。
此外,优点是您可以检查准备结果,如果准备失败,您的算法可以分支处理异常,而不会浪费数据库资源来运行失败的查询。