执行计划缓存,Prepared Statement性能跃升的秘密

Posted 华为云开发者社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了执行计划缓存,Prepared Statement性能跃升的秘密相关的知识,希望对你有一定的参考价值。

摘要:一起看一下GaussDB(for MySQL)是如何对执行计划进行缓存并加速Prepared Statement性能的。

本文分享自华为云社区《执行计划缓存,Prepared Statement性能跃升的秘密》,作者: GaussDB 数据库。

引言

在数据库系统中,SQL(Structured Query Language)语句输入到系统后,一般要经历:词法语法解析(parse)、重写(resolve)、优化(optimize)、执行(execute)的过程。词法语法分析,重写和优化,这三个阶段会生成SQL语句的执行计划 (plan)。当SQL语句存在多种执行计划的时候,优化器会从这许多的执行计划中挑选出一个它认为最优的(通常是占用系统资源最少的,包括CPU以及IO等)作为最终的执行计划供执行器执行。生成执行计划的过程会消耗较多的时间,特别是存在许多可选的执行计划时。

图1:SQL语句执行

Prepared Statement是将SQL语句中的值用占位符替代,可以视为将SQL语句模板化或者说参数化。当执行PREPARE语句时,传统MySQL将对指定的语句进行词法语法解析和重写,如上图①②。该阶段称为预编译阶段。Prepared Statement的优势在于一次编译、多次运行,省去了预编译阶段需要的时间。随后发出EXECUTE命令时,MySQL将对编译阶段生成的结构执行优化,即上图的③,生成对应的执行计划并执行,把输出结果返回到客户端。例如:

PREPARE stmt FROM ‘SELECT * FROM t WHERE t.a = ?’;
SET @var = 2;
EXECUTE stmt USING @var;

传统MySQL的Prepared Statement只会节省SQL语句的解析及重写过程需要的时间,但是对于一条SQL语句,如文章开头所述,优化SQL语句并生成执行计划需要耗费大量的资源以及时间。如果能将该Prepared Statement语句对应的最终执行计划进行缓存,当执行EXECUTE语句的时候,就可以直接使用已缓存的执行计划,从而就可以跳过SQL语句生成执行计划的整个过程,进而可以提高语句的执行性能。为此,GaussDB(for MySQL) 提供了Prepared Statement执行计划缓存特性。

接下来一起看一下GaussDB(for MySQL)是如何对执行计划进行缓存并加速Prepared Statement性能的。

执行计划缓存工作原理

GaussDB(for MySQL)对Prepared Statement执行计划进行缓存的基本原理和流程如下图所示:

  • 响应EXECUTE,执行查询。
  • 通过is_plan_cached过程来查看当前Query的执行计划是否已经被缓存。
  • 如果已经被缓存,优化器将对当前的Query缓存的执行计划进行初始化,根据执行计划的上下文还原执行计划,然后利用还原的执行计划继续执行。
  • 如果没有被缓存,在执行完Query优化生成执行计划之后,通过is_query_cachable过程验证当前执行计划是否可以被缓存。
  • 如果满足缓存条件,执行计划将会被缓存(调用cache_JOIN_plan),以便以后的EXECUTE语句可以利用该缓存的计划进行执行。
  • 如果不能缓存,通过传统的MySQL执行流程(优化,生成执行计划然后执行)执行EXECUTE语句。

执行计划缓存管理

  • 执行计划缓存功能开关

GaussDB(for MySQL)引入了一个新的系统参数rds_plan_cache来开关Prepared Statement执行计划缓存功能。

rds_plan_cache:该参数可以设置为ON/OFF。分别代表开启和关闭执行计划缓存。该参数是Session/Global级别的参数。

  • 查看执行计划缓存情况

GaussDB(for MySQL)提供了两个状态变量供用户查看或者验证Prepared Statement执行计划是否被缓存,以及在执行时是否命中了缓存的执行计划。

  • cached_plan_count:显示有多少个Prepared Statement缓存了执行计划。这是一个Global级别的状态变量。
  • cached_plan_hits:显示EXECUTE执行过程中命中了缓存的执行计划的次数。这是一个Session/Global状态。

下面举例来看一下Prepared Statement是如何利用了执行计划缓存特性的:

SET @a = \'two\';
SET @b = 3;
PREPARE stmt FROM "SELECT * FROM t1 WHERE b = ? AND c = ?";
EXECUTE stmt USING @a,@b;

执行结果如下:

a b c
6 two 3

再次执行Prepared Statement:

EXECUTE stmt USING @a,@b;
a b c
6 two 3

第三次执行Prepared Statement:

execute stmt using @a,@b;
a b c
6 two 3

通过cached_plan_count和cached_plan_hits查看stmt执行计划是否被缓存,以及在执行时是否命中了缓存的执行计划。

SHOW SESSION STATUS LIKE "cached_plan%";

显示结果如下:

Variable_name Value
Cached_plan_count 1
Cached_plan_hits 2

从显示结果可以看出,第一次执行EXECUTE语句的时候,Prepared Statement对执行计划进行了缓存,即可以看到Cached_plan_count为1; 之后执行两次EXECUTE语句,都命中了执行计划缓存,所以可以看到Cached_plan_hits变成了2。

缓存的执行计划如何失效

为了保持当前缓存的执行计划是尽可能最优的,GaussDB(for MySQL)定义了如下规则来对当前缓存的计划进行失效,并重新生成执行计划:

  • 执行计划相关表的记录数更改超过总记录数的20%。
    这意味着当前表的记录数如果插入/删除超过20%的记录,当前缓存计划将失效并在优化后重新缓存。注:记录数是根据统计数据估计的。所以最好先对表进行Analyze。
  • 表定义进行了更改。
    例如,执行计划相关表上进行的DDL将导致缓存计划无效,并在优化后重新缓存。
  • 如果系统变量Optimizer_switch中影响执行计划生成的选项值进行了更改,则缓存的计划将失效,并在优化后重新缓存。
  • 系统字符集发生变化,与缓存的计划不同时,将导致缓存计划失效,并在优化后重新缓存。

执行计划缓存功能当前的一些限制

GaussDB(for MySQL)的Prepared Statement的目的是节约查询的优化时间。对于通过并行查询优化的大查询,也就是数据量相对庞大的查询,这些查询大部分的执行时间是集中在执行计划的执行阶段。对于该类型的查询,优化时间相比执行时间而言可以忽略不计,所以GaussDB(for MySQL)没有对并行查询计划进行缓存。另外,GaussDB(for MySQL)对于Prepared statement 缓存执行计划的能力还在逐步增强中,比如当前只支持单表的SELECT查询语句,暂时还不支持UNION操作。

执行计划缓存性能测试结果

对于使用执行计划缓存和不使用执行计划缓存的场景,基于Sysbench测试集进行了性能测试对比,从测试结果可以看出,在启用执行计划缓存后,各类业务性能均有提升。注意:这些测试只代表相对数字,并不代表实际性能。

测试环境配置如下:

数据集 : 8 个表,每个表1000万行
测试服务器:Intel(R) Xeon(R) CPU E5-2690 v4 @ 2.60GHz 2 physical cores 56 processors 460G memory

总结

GaussDB(for MySQL)通过缓存执行计划,可以提升Prepared Statement的性能。特别是针对Range Scan的测试集,性能提升可达2倍左右。未来我们会支持越来越多的查询场景,性能加速值得期待。

 

点击关注,第一时间了解华为云新鲜技术~

PDO Prepared Statement 允许执行 javascript

【中文标题】PDO Prepared Statement 允许执行 javascript【英文标题】:PDO Prepared Statement allows javascript to be executed 【发布时间】:2014-12-17 10:32:17 【问题描述】:

我真的对 PDO Prepared Statement 感到困惑。我按照 php.net 文档中的说明做了所有事情。当我将 JavaScript 插入数据库,然后查询数据库并在页面上输出结果时,它给了我在数据库中的 JavaScript 警报。

PDO 是否可以保护用户数据免受此类攻击,或者我们是否必须自己进行清理和转义,或者这里是否遗漏了什么。

<?php
    try 
        $db = new PDO('mysql:host=localhost;dbname=test', "root", "");
     catch ( PDOException $e ) 
        print "Error!: " . $e->getMessage() . "<br/>";
        die();
    

    $query = $db->query('SELECT * from users');
    if( $query->rowCount() )
        $rows = $query->fetchAll( PDO::FETCH_OBJ);
        foreach( $rows as $row )
            echo $row->user_name;
            echo "<br/>";
        
     else 
        echo "No results found";
    

    $user_name = "<script type=\"text/javascript\">alert(\"Works\");</script>";
    $user_email = "example@gmail.com";
    $user_password = "password";
    $user_status = "1";

    $data = array(
        ":user_name" => $user_name,
        ":user_email" => $user_email,
        ":user_password" => $user_password,
        ":user_status" => $user_status
    );
    $sql = "INSERT INTO users (user_name, user_email, user_password, user_status) VALUES(:user_name, :user_email, :user_password, :user_status)";
    $prepare = $db->prepare($sql);
    $exec = $prepare->execute($data);
?>

【问题讨论】:

PDO 不会清理任何东西。如果您使用准备好的语句,它们提供了一种将数据放入不易受到 SQL 注入攻击的 SQL 数据库的方法。您有责任防御所有其他漏洞。您应该使用htmlspecialchars() 或其一些变体来编码您的数据以供输出。 输出转义必须针对特定上下文进行(此处为htmlspecialchars(),在任何echo 语句之前),并且它不是要处理的数据库接口的域。 【参考方案1】:

当您使用准备好的语句并参数化您的 SQL 时,PDO 可以防止 SQL 注入攻击。

但是您正在描述一种称为跨站点脚本 (XSS) 的东西。这是一个完全不同的安全问题。它不依赖于 SQL 或数据库内容。您可以使用任何应用程序数据创建 XSS 漏洞,而不仅限于来自数据库的数据。 XSS 和 SQL 注入一样,对于所有程序员来说都很重要。

您可能已经看过关于如何在 PHP 中防止 SQL 注入的高评价文章:

How can I prevent SQL-injection in PHP?

但这里有一些关于 XSS 防御的好帖子:

What are the best practices for avoiding xss attacks in a PHP site How to prevent XSS with HTML/PHP? Will HTML Encoding prevent all kinds of XSS attacks?

SQL 注入和跨站点脚本一直是在网络上暴露数据的两大安全错误。请参阅 OWASP 十大关键安全风险列表:https://www.owasp.org/index.php/OWASP_Top_Ten_Project

让自己访问那个 OWASP 站点。那里有很多资源可以了解安全风险以及如何解决这些风险。而且是免费的!

【讨论】:

我也喜欢这篇***文章~en.wikipedia.org/wiki/Secure_input_and_output_handling。还有这个更具体的 PHP ~ lukeplant.me.uk/blog/posts/why-escape-on-input-is-a-bad-idea

以上是关于执行计划缓存,Prepared Statement性能跃升的秘密的主要内容,如果未能解决你的问题,请参考以下文章

如何为两个查询条件执行单个 Prepared Statement

存储过程执行计划缓存查询

Laravel - PDO Prepared Statement - 在其他无缓冲查询处于活动状态时无法执行查询

JAVA JDBC mySql Prepared statement 更新查询 [重复]

Prepared Statement 因 DB2 SQL 错误而失败

使用prepared statement来防止SQL注入会影响性能吗?