使用绑定变量 运行 Oracle 单记录 update/insert 有优势吗?

Is there an advantage to use bound variables running Oracle single record update/insert?

我被 Oracle 管理员缠扰,告诉我如果我使用绑定变量而不是内联变量,Oracle 可以优化更好的请求。代码是 C# 并使用 Devart Oracle 提供程序 Oracle12c+。我知道如果你 运行 一个 select - 缓存的统计数据和下一个类似的命令会做得更好,它会产生影响。但是单行 insert/update?我不服。

谁能给个知情的答案?

示例:

update x set a = 0 where id = 100

vs 

update x set a = :a where id = :id

编辑:未绑定的值仅为数字,因此 SQL 注入超出了问题的范围

如果您通过用户输入的字符串连接构建查询,您很容易受到 SQL 注入攻击。明智的 DB 管理员永远不想做 this call。它甚至不关心你做了什么样的操作 - SQL 注入基本上可以 运行 随机 SQL 代码。

现在确切的替代方法因您的编程语言而异。在 C# 中,您使用 Parametized Queries。不仅那些 SQL 注入证明,它还提供了一些类型检查并且应该更快。就我个人而言,我什至发现它也更具可读性。无需制作引号迷宫。

然而,正如您提到的绑定变量,您可能正在使用 Prepared Statements。老实说:我很少使用它们。它们看起来比它的价值要复杂得多。但如果这是您的图书馆和语言支持的唯一非串联方式,那就是您必须采用的方式。

请注意,在极少数情况下您可以使用串联。那就是如果字符串不是从磁盘、网络或用户输入中读取的。我最近有一个案例,根据用户输入,必须附加一个 IN 子句。根据输入,它是不同的(值和长度)。而且我使用的是准备好的语句,但处理得不是很好。然而,由于要追加的字符串 picked/generated 100% 在 switch/case 语句中从头开始(已提供用户输入),因此没有危险。最多随机用户输入可以达到默认情况。

通过简单的 UPDATEINSERT 语句使用绑定变量有 巨大的 性能优势。

首先,由于不必解析如此多的独特语句,性能得到了直接提升。一个小 UPDATE 乍一看似乎是一件简单的事情,但在幕后发生了很多事情。必须解析每个唯一的语句——Oracle 必须检查每个语句的语法和安全性。使用绑定变量,该工作只发生一次。下面的简单测试显示 UPDATE 运行 使用绑定变量的速度提高了 10 多倍。

-- Create simple table with one record.
create table x(id number, a number);
insert into x values(100, 0);
commit;


-- 10,000 concatenated UPDATES - 5 seconds.
begin
    for i in 1 .. 10000 loop
        execute immediate 'update x set a = '||i||' where id = 100';
    end loop;
end;
/


-- 10,000 bind variable UPDATES - 0.3 seconds.
-- (Execute immediate is used here keep this test similar to above.)
begin
    for i in 1 .. 10000 loop
        execute immediate 'update x set a = :i where id = 100' using i;
    end loop;
end;
/

此外,使用绑定变量可以让您使用批处理并将性能再提高 10 倍。

使用绑定变量意味着只有一个 SQL_ID,这使得 DBA 的性能调整和跟踪变得更加容易。使用单个 SQL_ID,可以使用许多计划管理选项,以防出现错误的执行计划。

最后,创建大量独特的语句将填满共享池(用于包含执行计划信息的内存结构)。大量语句可能会将其他语句推出池,间接导致其他性能问题。

明确的答案促使我做一个基准测试。它模仿精确的产品服务器条件。

30000 次迭代的结果(差异为 6.5%):

Inline vars: 00:01:09.7444764
Bound  vars: 00:01:05.4454827
static void Main(string[] args)
{
    var LEN = 30000;
    var cmd = new OracleCommand();
    var sql = "update some_table set progress={0} where id=100";
    var rnd = new Random((int)DateTime.Now.Ticks);

    //

    var sw = new Stopwatch();
    sw.Start();

    for (int i = 0; i < LEN; i++)
    {
        using(var cnn = new OracleConnection("xxx"))
        {
            sql = string.Format(sql, rnd.Next());
            cmd.CommandText = sql;
            cmd.Connection = cnn;

            cnn.Open();
            cmd.ExecuteNonQuery();
        }
    }

    sw.Stop();

    Console.WriteLine("Inline vars: {0}", sw.Elapsed);

    //

    sw.Restart();

    sql = "update tm_fnet set progress=:p where id=:i";

    for (int i = 0; i < LEN; i++)
    {
        using (var cnn = new OracleConnection("xxx"))
        {
            cmd.CommandText = sql;
            cmd.Connection = cnn;
                        cmd.Parameters.Clear();
            cmd.Parameters.AddWithValue("i", 100);
            cmd.Parameters.AddWithValue("p", rnd.Next());

            cnn.Open();
            cmd.ExecuteNonQuery();
        }
    }

    sw.Stop();

    Console.WriteLine("Bound vars: {0}", sw.Elapsed);

    //

    Console.ReadKey(false);
}

结论 - 它并不那么重要,除非你必须照顾 SQL 注射(但这与我的情况无关 ).