mysql进阶之存储过程

往往看别人的代码会有这样的感慨:

看不懂

理还乱

是离愁

别是一番滋味在心头

为什么要使用存储过程?

在mysql开发中使用存储过程的理由:

  1. 当希望在不同的应用程序或平台上执行相同的函数,或者封装特定功能时,存储过程是非常有用的
  2. mysql 执行语句是要先编译,然后再执行的。这样如果查询并发大的时候。会浪费很多资源和时间。造成mysql进程占用资源过多,症状就是慢。但存储过程可以把一些特别的语句封装成一个方法 ,再编译好成一个可以执行的方法,对外只要接收参数就可以了。这样就不用再编译。执行就快了。你觉得你数据库因为同时出现太多读写操作而变得慢 ,那么就要用了
  3. 使用了存过程,很多相似性的删除,更新,新增等操作就变得轻松了,并且以后也便于管理!
  4. 存储过程因为SQL语句已经预编绎过了,因此运行的速度比较快。
  5. 存储过程可以接受参数、输出参数、返回单个或多个结果集以及返回值。可以向程序返回错误原因。
  6. 存储过程可以接受参数、输出参数、返回单个或多个结果集以及返回值。可以向程序返回错误原因。
  7. 存储过程运行比较稳定,不会有太多的错误。只要一次成功,以后都会按这个程序运行。
  8. 存储过程主要是在服务器上运行,减少对客户机的压力。
  9. 存储过程可以包含程序流、逻辑以及对数据库的查询。同时可以实体封装和隐藏了数据逻辑。
  10. 存储过程可以在单个存储过程中执行一系列SQL语句。
  11. 存储过程可以从自己的存储过程内引用其它存储过程,这可以简化一系列复杂语句。

存储过程案例

让我们使用一些简单的示例来了解什么是存储过程,使用下边演示程序的前提是必须正确安装了mysql。

下边的程序来源于[PHP和MySQL WEB开发(4th)]这本书,我们使用mysql中的books数据库,这个数据库中的表有一下几个:

mysql> show tables;
+-----------------+
| Tables_in_books |
+-----------------+
| book_reviews    |
| books           |
| customers       |
| order_items     |
| orders          |
+-----------------+
5 rows in set (0.00 sec)

我们用到了orders这个表:

mysql> select * from orders;
+---------+------------+--------+------------+
| orderid | customerid | amount | date       |
+---------+------------+--------+------------+
|       1 |          3 |  69.98 | 2007-04-02 |
|       2 |          1 |  49.99 | 2007-04-15 |
|       3 |          2 |  74.98 | 2007-04-19 |
|       4 |          3 |  24.99 | 2007-05-01 |
|       5 |          3 |  69.98 | 2007-04-02 |
|       6 |          1 |  49.99 | 2007-04-15 |
|       7 |          2 |  74.98 | 2007-04-19 |
|       8 |          3 |  24.99 | 2007-05-01 |
|       9 |          2 |  69.98 | 2008-09-02 |
|      10 |          3 |  45.90 | 2009-09-09 |
+---------+------------+--------+------------+
10 rows in set (0.00 sec)

示例1

程序basic_stored_procedure.sql

# Basic stored procedure example

delimiter //

create procedure total_orders (out total float)
begin
 select sum(amount) into total from orders;
end
//

delimiter ;

打开终端并启动mysql,使用有权限的账户登录mysql,使用某个数据库。上边的程序basic_stored_procedure.sql是封装在一个后缀为sql的文件中的,因此要使用mysql调用这个文件。

使用命令source /path/basic_stored_procedure.sql,path是你保存这个文件的路径。

然后使用命令call total_orders(@t);。可以看出,使用关键字call来调用该过程。call total_orders(@t);这个语句将调用total_orders过程并传入一个用来保存结果的变量。要查看该结果,需要查看该变量。使用下边的语句:

select @t;
结果为:

mysql> call total_orders(@t);
Query OK, 1 row affected (0.01 sec)

mysql> select @t;
+------------------+
| @t               |
+------------------+
| 555.760009765625 |
+------------------+
1 row in set (0.00 sec)

现在我们逐行分析程序basic_stored_procedure.sql中的代码:

  • elimiter // 将语句末尾的分隔符从当前值(这个分隔符通常是分号,除非以前改变了分隔符)改为双斜杠字符。这样做的目的是可以在存储过程中使用分号分隔符,这样mysql就会将分号当做是存储过程的代码,不会执行这些代码

  • create procedure total_orders (out total float) 创建了实际的存储过程,该存储过程的名称是total_orders。他只有一个total参数,该参数是需要计算的值。out表示该参数将被传出或返回

    • 参数也可以声明为in,表示该值必须传入到存储过程。inout表示该值必须被传入,但可以被存储过程修改
    • float 表示参数的类型。在这个例子中将返回所有订单的总数。orders列的类型为float,因此该返回类型也必须是float。可接受的数据类型映射到可供使用的列类型
    • 如果希望使用多个参数,可以提供一个由逗号间隔的参数列表
  • BEGINEND就好比函数中的{ },用来标识一个语句块

  • select sum(amount) into total from orders; 这就是我们实际中使用的查询语句

  • delimiter ; delimiter重新把分隔符定义为分号;

示例2

示例1中是使用的procedure创建过程的方法,在示例2中,我们将使用function来创建函数。函数接受参数并返回一个唯一值。

程序basic_function.sql:

# Basic syntax to create a function

delimiter //

create function add_tax (price float) returns float
  return price*1.1;

//

delimiter ;
  • 参数不必通过in或out指定,因为所有参数都是in,或是输入参数。
  • 在参数列表之后是returns float,它指定了返回值的类型。该值可以使任何有效的mysql类型
  • return price*1.1; 使用return可以返回一个值
  • 这里并没有使用beginend。可以使用它们,但他们不是必须的。就像php或者c中,如果一个语句块只含有一个语句,可以以调用内置函数的相同方式调用一个存储函数

使用方法示例1中有所不同。

select add_tax(100);
结果如下:

mysql> select add_tax(100);
+--------------+
| add_tax(100) |
+--------------+
|          110 |
+--------------+
1 row in set (0.01 sec)

查询或删除存储过程

在定义了过程和函数之后可使用下边语句来查看过程或函数的代码:

查询:

show create procedure total_orders;

show create function add_tax;

删除:

drop procedure total_orders;

drop function add_tax;

局部变量

使用declare语句,可以在begin...end语句块之间声明局部变量,就像函数中的局部变量一样。例如,可以对add_tax()函数进行修改,使其使用一个局部变量来保存税率,如下:

程序basic_function_with_variables.sql

# Basic syntax to create a function

delimiter //

create function add_tax (price float) returns float
begin
  declare tax float default 0.10;
  return price*(1+tax);
end
//

delimiter ;

游标和控制结构(一个更复杂的例子)

在下边的这个例子中,我们将编写一个存储过程,该存储过程将计算出最大金额的订单,并且返回该订单的orderid(很明显一个简单的查询,就可以计算出该数目,但是这个简单的例子只是说明了如何使用游标和控制结构)

程序control_structures_cursors.sql:

# Procedure to find the orderid with the largest amount
# could be done with max, but just to illustrate stored procedure principles

delimiter //

create procedure largest_order(out largest_id int)
begin
  declare this_id int;
  declare this_amount float;
  declare l_amount float default 0.0;
  declare l_id int;

  declare done int default 0;
  declare c1 cursor for select orderid, amount from orders;
  declare continue handler for sqlstate '02000' set done = 1;
  
  open c1;
  repeat
    fetch c1 into this_id, this_amount;
    if not done then
      if this_amount > l_amount then
        set l_amount=this_amount;
        set l_id=this_id;
      end if;
    end if;
   until done end repeat;
  close c1;

 
  set largest_id=l_id;

end
//

delimiter ;

在该存储过程的开始处,声明了一些在存储过程中使用的局部变量:

  • this_id 保存当前行的orderid
  • this_amount 保存当前行的amount
  • l_id 保存最大金额的orderid
  • l_amount 保存最大金额的amount 默认值为0.0
  • done 用于循环中的标记,当循环结束后会被标记为1,默认为0,也就是false

declare continue handler for sqlstate '02000' set done = 1;是一个声明句柄,它类似于存储过程中的一个异常。

这里边有一个关键字continue,这个关键字是和exit关键字相对应的。continue语句会执行完指定操作后继续循环,而exit语句会退出将从最近的begin...end语句块中退出。 在这里的指定的操作就是set done = 1

既然声明了句柄,就要告诉程序句柄在什么时候调用,在这个例子中,当sqlstate '02000'语句被调用时会执行句柄。那么这个sqlstate '02000'是什么意思呢?该句柄将在无法再找到记录行后调用。也就是说当遍历完所有的结果集后就会调用。

因此,declare continue handler for sqlstate '02000' set done = 1;的意思就是当遍历完结果集之后把done的值设为1。

游标(Cursor)是处理数据的一种方法,为了查看或者处理结果集中的数据,游标提供了在结果集中一次以行或者多行前进或向后浏览数据的能力。我们可以把游标当作一个指针,它可以指定结果中的任何位置,然后允许用户对指定位置的数据进行处理。(关于游标的详细信息,请看这篇博文SQLServer游标(Cursor)简介和使用说明

注意,句柄要定义在游标之后,不然会报ERROR 1338 (42000): Cursor declaration after handler declaration错误。

declare c1 cursor for select orderid, amount from orders;

这个游标名称为c1,这只是他要保存内容的定义,该查询还不会执行。使用open c1;来真正运行这个查询。

要获得每一个数据行,必须运行一个fetch语句。可以在一个repeat循环中完成此操作。:

repeat
...
until done end repeat;

只有在循环的尾部才检测done的值,除了使用repeat还可以使用while和loop循环

while condition do
...
end while

loop
...
end loop

这些循环没有内置的循环条件,但是可以通过leave语句退出循环。请注意,存储过程不支持for循环

fetch c1 into this_id, this_amount; 这行代码将获得一个数据行,并把查询到的两个属性保存到this_idthis_amount中。

if not done then
  if this_amount > l_amount then
    set l_amount=this_amount;
    set l_id=this_id;
  end if;
end if;

close c1; 将关闭这个游标

set largest_id=l_id; 将最大的值赋值给out参数,不能使用局部变量给外部调用。

调用方法:

call largest_order(@l);
select @l;

结果如下:

mysql> call largest_order(@l);
Query OK, 0 rows affected (0.00 sec)

mysql> select @l;
+------+
| @l   |
+------+
|    3 |
+------+
1 row in set (0.01 sec)

说明

如有错误之处,请给予指出,多谢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • 任务需求:定时执行的任务,调用存储过程,进行数据迁移。 存储过程相关总结:(存储过程的创建 不能伴随有if exi...
    时待吾阅读 3,057评论 0 4
  • oracle存储过程常用技巧 我们在进行pl/sql编程时打交道最多的就是存储过程了。存储过程的结构是非常的简单的...
    dertch阅读 3,472评论 1 12
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • 转载自这里 存储过程简介 我们常用的操作数据库语言SQL语句在执行的时候需要要先编译,然后执行,而存储过程(Sto...
    杜七阅读 2,379评论 4 27
  • 当一个大型系统在建立时,会发现,很多的SQL操作是有重叠的,个别计算是相同的,比如:业务系统中,计算一张工单的计算...
    JackFrost_fuzhu阅读 3,366评论 0 27