MySQL prepare原理详解

Prepare的好处 

Prepare SQL产生的原因。首先从mysql服务器执行sql的过程开始讲起,SQL执行过程包括以下阶段 词法分析->语法分析->语义分析->执行计划优化->执行。词法分析->语法分析这两个阶段我们称之为硬解析。词法分析识别sql中每个词,语法分析解析SQL语句是否符合sql语法,并得到一棵语法树(Lex)。对于只是参数不同,其他均相同的sql,它们执行时间不同但硬解析的时间是相同的。而同一SQL随着查询数据的变化,多次查询执行时间可能不同,但硬解析的时间是不变的。对于sql执行时间较短,sql硬解析的时间占总执行时间的比率越高。而对于淘宝应用的绝大多数事务型SQL,查询都会走索引,执行时间都比较短。因此淘宝应用db sql硬解析占的比重较大。

Prepare的出现就是为了优化硬解析的问题。Prepare在服务器端的执行过程如下

1)  Prepare 接收客户端带”?”的sql, 硬解析得到语法树(stmt->Lex), 缓存在线程所在的preparestatement cache中。此cache是一个HASH MAP. Key为stmt->id. 然后返回客户端stmt->id等信息。

2)  Execute 接收客户端stmt->id和参数等信息。注意这里客户端不需要再发sql过来。服务器根据stmt->id在preparestatement cache中查找得到硬解析后的stmt, 并设置参数,就可以继续后面的优化和执行了。

Prepare在execute阶段可以节省硬解析的时间。如果sql只执行一次,且以prepare的方式执行,那么sql执行需两次与服务器交互(Prepare和execute), 而以普通(非prepare)方式,只需要一次交互。这样使用prepare带来额外的网络开销,可能得不偿失。我们再来看同一sql执行多次的情况,比如以prepare方式执行10次,那么只需要一次硬解析。这时候  额外的网络开销就显得微乎其微了。因此prepare适用于频繁执行的SQL。

Prepare的另一个作用是防止sql注入,不过这个是在客户端jdbc通过转义实现的,跟服务器没有关系。
硬解析的比重

压测时通过perf 得到的结果,硬解析相关的函数比重都比较靠前(MYSQLparse 4.93%, lex_one_token 1.79%, lex_start 1.12%)总共接近8%。因此,服务器使用prepare是可以带来较多的性能提升的。

jdbc与prepare 

jdbc服务器端的参数:

useServerPrepStmts:默认为false. 是否使用服务器prepare开关

jdbc客户端参数:

cachePrepStmts:默认false.是否缓存prepareStatement对象。每个连接都有一个缓存,是以sql为唯一标识的LRU cache. 同一连接下,不同stmt可以不用重新创建prepareStatement对象。

prepStmtCacheSize:LRU cache中prepareStatement对象的个数。一般设置为最常用sql的个数。

prepStmtCacheSqlLimit:prepareStatement对象的大小。超出大小不缓存。

Jdbc对prepare的处理过程: 

useServerPrepStmts=true时Jdbc对prepare的处理

1)  创建PreparedStatement对象,向服务器发送COM_PREPARE命令,并传送带问号的sql. 服务器返回jdbc stmt->id等信息

2)  向服务器发送COM_EXECUTE命令,并传送参数信息。

useServerPrepStmts=false时Jdbc对prepare的处理

1)  创建PreparedStatement对象,此时不会和服务器交互。

2) 根据参数和PreparedStatement对象拼接完整的SQL,向服务器发送QUERY命令

我们再看参数cachePrepStmts打开时在useServerPrepStmts为true或false时,均缓存PreparedStatement对象。只不过useServerPrepStmts为的true缓存PreparedStatement对象包含服务器的stmt->id等信息,也就是说如果重用了PreparedStatement对象,那么就省去了和服务器通讯(COM_PREPARE命令)的开销。而useServerPrepStmts=false是,开启cachePrepStmts缓存PreparedStatement对象只是简单的sql解析信息,因此此时开启cachePrepStmts意义不是太大。

我们来开看一段java代码

Connection con = null;
      PreparedStatement ps = null;
      String sql = "select * from user where id=?";
      ps = con.prepareStatement(sql);
      ps.setInt(1, 1);‍‍
      ps.executeQuery();
      ps.close();
      ps = con.prepareStatement(sql);
      ps.setInt(1, 3);
      ps.executeQuery();
      ps.close();

这段代码在同一会话中两次prepare执行同一语句,并且之间有ps.close();

useServerPrepStmts=false时,服务器会两次硬解析同一SQL。

useServerPrepStmts=true, cachePrepStmts=false时服务器仍然会两次硬解析同一SQL。

useServerPrepStmts=true, cachePrepStmts=true时服务器只会硬解析一次SQL。

如果两次prepare之间没有ps.close();那么cachePrepStmts=true,cachePrepStmts=false也只需一次硬解析.

因此,客户端对同一sql,频繁分配和释放PreparedStatement对象的情况下,开启cachePrepStmts参数是很有必要的。

测试

1)做了一个简单的测试,主要测试prepare的效果和useServerPrepStmts参数的影响.

cnt = 5000;
    // no prepare
    String sql = "select biz_order_id,out_order_id,seller_nick,buyer_nick,seller_id,buyer_id,auction_id,auction_title,auction_price,buy_amount,biz_type,sub_biz_type,fail_reason,pay_status,logistics_status,out_trade_status,snap_path,gmt_create,status,ifnull(buyer_rate_status, 4) buyer_rate_status from tc_biz_order_0030 where " +
    "parent_id = 594314511722841 or parent_id =547667559932641;";
    begin = new Date();
    System.out.println("begin:" + df.format(begin));
    stmt = con.createStatement();
    for (int i = 0; i < cnt; i++)
    {
      stmt.executeQuery(sql);
    }
    end = new Date();
    System.out.println("end:" + df.format(end));
    long temp = end.getTime() - begin.getTime();
    System.out.println("no perpare interval:" + temp);

    // test prepare
    sql = "select biz_order_id,out_order_id,seller_nick,buyer_nick,seller_id,buyer_id,auction_id,auction_title,auction_price,buy_amount,biz_type,sub_biz_type,fail_reason,pay_status,logistics_status,out_trade_status,snap_path,gmt_create,status,ifnull(buyer_rate_status, 4) buyer_rate_status from tc_biz_order_0030 where " +
        "parent_id = 594314511722841 or parent_id =?;";
    ps = con.prepareStatement(sql);
    BigInteger param = new BigInteger("547667559932641");
    begin = new Date();
    System.out.println("begin:" + df.format(begin));
    for (int i = 0; i < cnt; i++)
    {
      ps.setObject(1, param);
      ps.executeQuery();
    }
    end = new Date();
    System.out.println("end:" + df.format(end));
    temp = end.getTime() - begin.getTime();
    System.out.println("prepare interval:" + temp);

经多次采样测试结果如下

非prepare和prepare时间比
useServerPrepStmts=true 0.93
useServerPrepStmts=false 1.01

结论:

useServerPrepStmts=true时,prepare提升7%;

useServerPrepStmts=false时,prepare与非prepare性能相当。

如果将语句简化为select * from tc_biz_order_0030 where parent_id =?。那么测试的结论useServerPrepStmts=true时,prepare仅提升2%;sql越简单硬解析的时间就越少,prepare的提升就越少。

注意:这个测试是在单个连接,单条sql的理想情况下进行的,线上会出现多连接多sql,还有sql执行频率,sql的复杂程度等不同,因此prepare的提升效果会随具体环境而变化。

2)prepare 前后的perf top 对比 

以下为非prepare

6.46%  mysqld mysqld       [.] _Z10MYSQLparsePv
   3.74%  mysqld libc-2.12.so    [.] __memcpy_ssse3
   2.50%  mysqld mysqld       [.] my_hash_sort_utf8
   2.15%  mysqld mysqld       [.] cmp_dtuple_rec_with_match
   2.05%  mysqld mysqld       [.] _ZL13lex_one_tokenPvS_
   1.46%  mysqld mysqld       [.] buf_page_get_gen
   1.34%  mysqld mysqld       [.] page_cur_search_with_match
   1.31%  mysqld mysqld       [.] _ZL14build_templateP19row_prebuilt_structP3THDP5TABLEj
   1.24%  mysqld mysqld       [.] rec_init_offsets
   1.11%  mysqld libjemalloc.so.1  [.] free
   1.09%  mysqld mysqld       [.] rec_get_offsets_func
   1.01%  mysqld libjemalloc.so.1  [.] malloc
   0.96%  mysqld libc-2.12.so    [.] __strlen_sse42
   0.93%  mysqld mysqld       [.] _ZN4JOIN8optimizeEv
   0.91%  mysqld mysqld       [.] _ZL15get_hash_symbolPKcjb
   0.88%  mysqld mysqld       [.] row_search_for_mysql
   0.86%  mysqld [kernel.kallsyms]  [k] tcp_recvmsg

以下为perpare

3.46%  mysqld libc-2.12.so    [.] __memcpy_ssse3
   2.32%  mysqld mysqld       [.] cmp_dtuple_rec_with_match
   2.14%  mysqld mysqld       [.] _ZL14build_templateP19row_prebuilt_structP3THDP5TABLEj
   1.96%  mysqld mysqld       [.] buf_page_get_gen
   1.66%  mysqld mysqld       [.] page_cur_search_with_match
   1.54%  mysqld mysqld       [.] row_search_for_mysql
   1.44%  mysqld mysqld       [.] btr_cur_search_to_nth_level
   1.41%  mysqld libjemalloc.so.1  [.] free
   1.35%  mysqld mysqld       [.] rec_init_offsets
   1.32%  mysqld [kernel.kallsyms]  [k] kfree
   1.14%  mysqld libjemalloc.so.1  [.] malloc
   1.08%  mysqld [kernel.kallsyms]  [k] fget_light
   1.05%  mysqld mysqld       [.] rec_get_offsets_func
   0.99%  mysqld mysqld       [.] _ZN8Protocol24send_result_set_metadataEP4ListI4ItemEj
   0.90%  mysqld mysqld       [.] sync_array_print_long_waits
   0.87%  mysqld mysqld       [.] page_rec_get_n_recs_before
   0.81%  mysqld mysqld       [.] _ZN4JOIN8optimizeEv
   0.81%  mysqld libc-2.12.so    [.] __strlen_sse42
   0.78%  mysqld mysqld       [.] _ZL20make_join_statisticsP4JOINP10TABLE_LISTP4ItemP16st_dynamic_array
   0.72%  mysqld [kernel.kallsyms]  [k] tcp_recvmsg
   0.63%  mysqld libpthread-2.12.so [.] __pthread_getspecific_internal
   0.63%  mysqld [kernel.kallsyms]  [k] sk_run_filter
   0.60%  mysqld mysqld       [.] _Z19find_field_in_tableP3THDP5TABLEPKcjbPj
   0.60%  mysqld mysqld       [.] page_check_dir
   0.57%  mysqld mysqld       [.] _Z16dispatch_command19enum_server_commandP3THDP

对比可以发现 MYSQLparse lex_one_token在prepare时已优化掉了。

思考

1 开启cachePrepStmts的问题,前面谈到每个连接都有一个缓存,是以sql为唯一标识的LRU cache. 在分表较多,大连接的情况下,可能会个应用服务器带来内存问题。这里有个前提是ibatis是默认使用prepare的。 在mybatis中,标签statementType可以指定某个sql是否是使用prepare.

statementType Any one of STATEMENT, PREPARED or CALLABLE. This causes MyBatis to use Statement, PreparedStatement orCallableStatement respectively. Default: PREPARED.

这样可以精确控制只对频率较高的sql使用prepare,从而控制使用prepare sql的个数,减少内存消耗。遗憾的是目前集团貌似大多使用的是ibatis 2.0版本,不支持statementType
标签。

2 服务器端prepare cache是一个HASH MAP. Key为stmt->id,同时也是每个连接都维护一个。因此也有可能出现内存问题,待实际测试。如有必要需改造成Key为sql的全局cache,这样不同连接的相同prepare sql可以共享。

3 oracle prepare与mysql prepare的区别:

mysql与oracle有一个重大区别是mysql没有oracle那样的执行计划缓存。前面我们讲到SQL执行过程包括以下阶段 词法分析->语法分析->语义分析->执行计划优化->执行。oracle的prepare实际上包括以下阶段:词法分析->语法分析->语义分析->执行计划优化,也就是说oracle的prepare做了更多的事情,execute只需要执行即可。因此,oracle的prepare比mysql更高效。

总结

以上就是本文关于MySQL prepare原理详解的全部内容,感兴趣的朋友可以参阅本站其他相关专题,有什么问题或者需要的文章或者书籍和源码可以随时留言,小编将竭诚为您解答。感谢大家对本站的支持。

(0)

相关推荐

  • MySQL prepare语句的SQL语法

    MySQL prepare语法: PREPARE statement_name FROM preparable_SQL_statement; /*定义*/ EXECUTE statement_name [USING @var_name [, @var_name] ...]; /*执行预处理语句*/ {DEALLOCATE | DROP} PREPARE statement_name /*删除定义*/ ; PREPARE语句用于预备一个语句,并指定名称statement_name,以后引用该语句.

  • PHP5 mysqli的prepare准备语句使用说明

    mysqli对prepare的支持对于大访问量的网站是很有好处的,它极大地降低了系统开销,而且保证了创建查询的稳定性和安全性.prepare准备语句分为绑定参数和绑定结果,下面将会一一介绍! (1)绑定参数 看下面php代码: 复制代码 代码如下: <?php //创建连接 $mysqli=new mysqli("localhost","root","","volunteer"); //检查连接是否被创建 if (mys

  • MySQL中预处理语句prepare、execute与deallocate的使用教程

    前言 MySQL官方将prepare.execute.deallocate统称为PREPARE STATEMENT,我习惯称其为[预处理语句],其用法十分简单,下面话不多说,来一起看看详细的介绍吧. 示例代码 PREPARE stmt_name FROM preparable_stmt EXECUTE stmt_name [USING @var_name [, @var_name] ...] - {DEALLOCATE | DROP} PREPARE stmt_name 举个栗子: mysql>

  • php+mysql prepare 与普通查询的性能对比实例讲解

    php+mysql prepare 与普通查询的性能对比 实例代码如下: <?php class timer { public $StartTime = 0; public $StopTime = 0; public $TimeSpent = 0; function start(){ $this->StartTime = microtime(); } function stop(){ $this->StopTime = microtime(); } function spent() {

  • MySQL prepare原理详解

    Prepare的好处  Prepare SQL产生的原因.首先从mysql服务器执行sql的过程开始讲起,SQL执行过程包括以下阶段 词法分析->语法分析->语义分析->执行计划优化->执行.词法分析->语法分析这两个阶段我们称之为硬解析.词法分析识别sql中每个词,语法分析解析SQL语句是否符合sql语法,并得到一棵语法树(Lex).对于只是参数不同,其他均相同的sql,它们执行时间不同但硬解析的时间是相同的.而同一SQL随着查询数据的变化,多次查询执行时间可能不同,但硬解

  • MySQL索引原理详解

    目录 索引是什么 索引数据结构 树形索引 树的动画 为什么不是简单的二叉树? 为什么不是红黑树? 为什么最终选择B+树 而不是B树 水平方向可以存放更多的索引key 数据量估算 叶子节点包含所有的索引字段 叶子节点直接包含双向指针,范围查找效率高 Hash 索引 更快 不支持范围查询 hash 冲突问题 表引擎 MyISAM 和 InnoDB 引擎 MyISAM 引擎 InnoDB 表数据组织形式 聚集与非聚集索引 ★★★ 为什么建议InnoDB 表必须有主键,并且是整型自增的? 为什么是整型

  • MySQL备份原理详解

    本文为大家介绍了MySQL备份原理,欢迎大家阅读. 备份是数据安全的最后一道防线,对于任何数据丢失的场景,备份虽然不一定能恢复百分之百的数据(取决于备份周期),但至少能将损失降到最低.衡量备份恢复有两个重要的指标:恢复点目标(RPO)和恢复时间目标(RTO),前者重点关注能恢复到什么程度,而后者则重点关注恢复需要多长时间.这篇文章主要讨论MySQL的备份方案,重点介绍几种备份方式的原理,包括文件系统快照(LVM),逻辑备份工具Mysqldump,Mydumper,以及物理备份工具Xtraback

  • mysql中的mvcc 原理详解

    目录 简介 前言 一.mysql 数据写入磁盘流程 二.redo log 1.redolog 的整体流程 2.为什么需要 redo log 三.undo log 1.undo log 特点 2.undo log 类型 3.undo log 生成过程 4.undo log 回滚过程 5.undo log的删除 四.mvcc 1.什么是MVCC 2.MVCC组成 3.快照读与当前读 快照读 当前读 五.mvcc操作演示 1.READ COMMITTED 隔离级别 2.REPEATABLE READ 

  • Mysql MVCC机制原理详解

    什么是MVCC MVCC,全称Multi-Version Concurrency Control,即多版本并发控制.MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存. 我们知道,一般情况下我们使用mysql数据库的时候使用的是Innodb存储引擎,Innodb存储引擎是支持事务的,那么当多线程同时执行事务的时候,可能会出现并发问题.这个时候需要一个能够控制并发的方法,MVCC就起到了这个作用. Mysql的锁和事务隔离级别 在理解MVCC机制

  • Mysql数据库group by原理详解

    目录 引言 1. 使用group by的简单例子 2. group by 原理分析 2.1 explain 分析 2.2 group by 的简单执行流程 3. where 和 having的区别 3.1 group by + where 的执行流程 3.2 group by + having 的执行 3.3 同时有where.group by .having的执行顺序 3.4 where + having 区别总结 4. 使用 group by 注意的问题 4.1 group by一定要配合聚

  • Mysql中报错函数floor()函数和rand()函数的配合使用及原理详解

    目录 1. floor 函数 1.1 floor 函数的作用 2. rand() 函数 2.1 rand() 函数的作用 3. floor() 函数 配合 rand() 函数 3.1 两个组合函数的使用 4. 以 floor() 函数为主的报错注入 4.1 报错注入的组成部分 4.2 报错注入组成部分解析 4.3 显错注入 总结 1. floor 函数 1.1 floor 函数的作用 floor() 函数的作用是返回小于等于该值的最大整数 举例说明:select floor(1.1) from

  • MySQL COUNT(*)性能原理详解

    目录 前言 1.COUNT(1).COUNT(*)与COUNT(字段)哪个更快? 实验分析 实验结果 实验结论 2.COUNT(*)与TABLES_ROWS 3.COUNT(*)是怎么样执行的? 4.总结 前言 在实际开发过程中,统计一个表的数据量是经常遇到的需求,用来统计数据库表的行数都会使用COUNT(*),COUNT(1)或者COUNT(字段),但是表中的记录越来越多,使用COUNT(*)也会变得越来越慢,今天我们就来分析一下COUNT(*)的性能到底如何. 1.COUNT(1).COUN

  • MySql事务及ACID实现原理详解

    目录 逻辑架构和存储引擎 自动提交 特殊操作 ACID 特性 原子性 持久性 隔离性 脏读.不可重复读和幻读 事务隔离级别 MVCC 一致性 逻辑架构和存储引擎 自动提交 MySQL 中默认采用的是自动提交(autocommit)模式,如下所示: 在自动提交模式下,如果没有 start transaction 显式地开始一个事务,那么每个 sql 语句都会被当做一个事务执行提交操作. 通过如下方式,可以关闭 autocommit;需要注意的是,autocommit 参数是针对连接的,在一个连接中

  • MYSQL存储过程 注释详解

    目录 1.使用说明 2.准备 3.语法 3.1 变量及赋值 3.2 入参出参 3.3 流程控制-判断 3.4 流程控制-循环 3.5 流程控制-退出.继续循环 3.6 游标 3.7 存储过程中的handler 4.练习 4.1 利用存储过程更新数据 4.3 其他场景: 5.其他 5.1 characteristic 5.2 死循环处理 5.3 可以在select语句中写case 5.4 临时表 0.环境说明: 软件 版本 mysql 8.0 navicat 1.使用说明 存储过程时数据库的一个重

随机推荐