SQL基础篇(二)

一,存储过程

存储过程是程序化的 SQL,可以直接操作底层数据表,相比于面向集合的操作方式,能够实现一些更复杂的数据处理。存储过程可以说是由 SQL 语句和流控制语句构成的语句集合,它和我们之前学到的函数一样,可以接收输入参数,也可以返回输出参数给调用者,返回计算结果。

1,定义一个存储过程:

CREATE PROCEDURE 存储过程名称([参数列表])
BEGIN
    需要执行的语句
END    

和视图一样,我们可以删除已经创建的存储过程,使用的是 DROP PROCEDURE。如果要更新存储过程,我们需要使用 ALTER PROCEDURE。

下面我们来看下如何实现一个简单的存储过程:

CREATE PROCEDURE `add_num`(IN n INT)
BEGIN
       DECLARE i INT;
       DECLARE sum INT;
       
       SET i = 1;
       SET sum = 0;
       WHILE i <= n DO
              SET sum = sum + i;
              SET i = i +1;
       END WHILE;
       SELECT sum;
END

当我们需要再次使用这个存储过程的时候,直接使用 CALL add_num(50);即可。这里我传入的参数为 50,也就是统计 1+2+…+50 的积累之和,查询结果为。

如果你使用 Navicat 这个工具来管理 MySQL 执行存储过程,那么直接执行上面这段代码就可以了。如果用的是 MySQL,你还需要用 DELIMITER 来临时定义新的结束符。因为默认情况下 SQL 采用(;)作为结束符,这样当存储过程中的每一句 SQL 结束之后,采用(;)作为结束符,就相当于告诉 SQL 可以执行这一句了。但是存储过程是一个整体,我们不希望 SQL 逐条执行,而是采用存储过程整段执行的方式,因此我们就需要临时定义新的 DELIMITER,新的结束符可以用(//)或者($$)。如果你用的是 MySQL,那么上面这段代码,应该写成下面这样:

DELIMITER //
CREATE PROCEDURE `add_num`(IN n INT)
BEGIN
       DECLARE i INT;
       DECLARE sum INT;
       
       SET i = 1;
       SET sum = 0;
       WHILE i <= n DO
              SET sum = sum + i;
              SET i = i +1;
       END WHILE;
       SELECT sum;
END //
DELIMITER ;

我们再来看下存储过程的 3 种参数类型:

举个例子,这里会用到我们之前讲过的王者荣耀的英雄数据表 heros。假设我想创建一个存储类型 get_hero_scores,用来查询某一类型英雄中的最大的最大生命值,最小的最大魔法值,以及平均最大攻击值,那么该怎么写呢?

CREATE PROCEDURE `get_hero_scores`(
       OUT max_max_hp FLOAT,
       OUT min_max_mp FLOAT,
       OUT avg_max_attack FLOAT,  
       s VARCHAR(255)
       )
BEGIN
       SELECT MAX(hp_max), MIN(mp_max), AVG(attack_max) 
       FROM heros WHERE role_main = s INTO max_max_hp, min_max_mp, avg_max_attack;
END

你能看到我定义了 4 个参数类型,其中 3 个为 OUT 类型,分别为 max_max_hp、min_max_mp 和 avg_max_attack,另一个参数 s 为 IN 类型。这里我们从 heros 数据表中筛选主要英雄定位为 s 的英雄数据,即筛选条件为 role_main=s,提取这些数据中的最大的最大生命值,最小的最大魔法值,以及平均最大攻击值,分别赋值给变量 max_max_hp、min_max_mp 和 avg_max_attack。

2,流控制语句

  1. BEGIN…END:BEGIN…END 中间包含了多个语句,每个语句都以(;)号为结束符。
  2. DECLARE:DECLARE 用来声明变量,使用的位置在于 BEGIN…END 语句中间,而且需要在其他语句使用之前进行变量的声明。
  3. SET:赋值语句,用于对变量进行赋值。
  4. SELECT…INTO:把从数据表中查询的结果存放到变量中,也就是为变量赋值。
  5. IF…THEN…ENDIF:条件判断语句,我们还可以在 IF…THEN…ENDIF 中使用 ELSE 和 ELSEIF 来进行条件判断。
  6. CASE:CASE 语句用于多条件的分支判断,使用的语法是下面这样的。
  7. LOOP、LEAVE 和 ITERATE:LOOP 是循环语句,使用 LEAVE 可以跳出循环,使用 ITERATE 则可以进入下一次循环。如果你有面向过程的编程语言的使用经验,你可以把 LEAVE 理解为 BREAK,把 ITERATE 理解为 CONTINUE。
  8. REPEAT…UNTIL…END REPEAT:这是一个循环语句,首先会执行一次循环,然后在 UNTIL 中进行表达式的判断,如果满足条件就退出,即 END REPEAT;如果条件不满足,则会就继续执行循环,直到满足退出条件为止。
  9. WHILE…DO…END WHILE:这也是循环语句,和 REPEAT 循环不同的是,这个语句需要先进行条件判断,如果满足条件就进行循环,如果不满足条件就退出循环。

3,关于存储过程使用的争议

你能看到存储过程有很多好处:

  1. 首先存储过程可以一次编译多次使用。存储过程只在创造时进行编译,之后的使用都不需要重新编译,这就提升了 SQL 的执行效率。
  2. 其次它可以减少开发工作量。将代码封装成模块,实际上是编程的核心思想之一,这样可以把复杂的问题拆解成不同的模块,然后模块之间可以重复使用,在减少开发工作量的同时,还能保证代码的结构清晰。
  3. 还有一点,存储过程的安全性强,我们在设定存储过程的时候可以设置对用户的使用权限,这样就和视图一样具有较强的安全性。
  4. 最后它可以减少网络传输量,因为代码封装到存储过程中,每次使用只需要调用存储过程即可,这样就减少了网络传输量。同时在进行相对复杂的数据库操作时,原本需要使用一条一条的 SQL 语句,可能要连接多次数据库才能完成的操作,现在变成了一次存储过程,只需要连接一次即可。

存储过程虽然有诸如上面的好处,但缺点也是很明显的:

  1. 它的可移植性差,存储过程不能跨数据库移植,比如在 MySQL、Oracle 和 SQL Server 里编写的存储过程,在换成其他数据库时都需要重新编写。
  2. 其次调试困难,只有少数 DBMS 支持存储过程的调试。对于复杂的存储过程来说,开发和维护都不容易。
  3. 此外,存储过程的版本管理也很困难,比如数据表索引发生变化了,可能会导致存储过程失效。我们在开发软件的时候往往需要进行版本管理,但是存储过程本身没有版本控制,版本迭代更新的时候很麻烦。
  4. 最后它不适合高并发的场景,高并发的场景需要减少数据库的压力,有时数据库会采用分库分表的方式,而且对可扩展性要求很高,在这种情况下,存储过程会变得难以维护,增加数据库的压力,显然就不适用了。

3,总结

二,事务

事务保证了一次处理的完整性,也保证了数据库中的数据一致性。它是一种高级的数据处理方式,如果我们在增加、删除、修改的时候某一个环节出了错,它允许我们回滚还原。正是因为这个特点,事务非常适合应用在安全性高的场景里,比如金融行业等。

1,事务的特性:ACID

事务的特性:要么完全执行,要么都不执行。不过要对事务进行更深一步的理解,还要从事务的 4 个特性说起,这 4 个特性用英文字母来表达就是 ACID。

  1. A,也就是原子性(Atomicity)。原子的概念就是不可分割,你可以把它理解为组成物质的基本单位,也是我们进行数据处理操作的基本单位。
  2. C,就是一致性(Consistency)。一致性指的就是数据库在进行事务操作后,会由原来的一致状态,变成另一种一致的状态。也就是说当事务提交后,或者当事务发生回滚后,数据库的完整性约束不能被破坏。
  3. I,就是隔离性(Isolation)。它指的是每个事务都是彼此独立的,不会受到其他事务的执行影响。也就是说一个事务在提交之前,对其他事务都是不可见的。
  4. 最后一个 D,指的是持久性(Durability)。事务提交之后对数据的修改是持久性的,即使在系统出故障的情况下,比如系统崩溃或者存储介质发生故障,数据的修改依然是有效的。因为当事务完成,数据库的日志就会被更新,这时可以通过日志,让系统恢复到最后一次成功的更新状态。

在这四个特性中,原子性是基础,隔离性是手段,一致性是约束条件,而持久性是我们的目的。

事务的另一个特点就是持久性,持久性是通过事务日志来保证的。日志包括了回滚日志和重做日志。当我们通过事务对数据进行修改的时候,首先会将数据库的变化信息记录到重做日志中,然后再对数据库中对应的行进行修改。这样做的好处是,即使数据库系统崩溃,数据库重启后也能找到没有更新到数据库系统中的重做日志,重新执行,从而使事务具有持久性。

2,事务的控制

在 MySQL 中,InnoDB 是支持事务的,而 MyISAM 存储引擎不支持事务。

现在我们再来看下事务的常用控制语句都有哪些:

  1. START TRANSACTION 或者 BEGIN,作用是显式开启一个事务。
  2. COMMIT:提交事务。当提交事务后,对数据库的修改是永久性的。
  3. ROLLBACK 或者 ROLLBACK TO [SAVEPOINT],意为回滚事务。意思是撤销正在进行的所有没有提交的修改,或者将事务回滚到某个保存点。
  4. SAVEPOINT:在事务中创建保存点,方便后续针对保存点进行回滚。一个事务中可以存在多个保存点。
  5. RELEASE SAVEPOINT:删除某个保存点。
  6. SET TRANSACTION,设置事务的隔离级别。

使用事务有两种方式,分别为隐式事务和显式事务。隐式事务实际上就是自动提交,Oracle 默认不自动提交,需要手写 COMMIT 命令,而 MySQL 默认自动提交,当然我们可以配置 MySQL 的参数:

mysql> set autocommit =0;  //关闭自动提交

mysql> set autocommit =1;  //开启自动提交

3,示例

在 MySQL 的默认状态下,自动提交打开。

CREATE TABLE test(name varchar(255), PRIMARY KEY (name)) ENGINE=InnoDB;
BEGIN;
INSERT INTO test SELECT '关羽';
COMMIT;
BEGIN;
INSERT INTO test SELECT '张飞';
INSERT INTO test SELECT '张飞';
ROLLBACK;
SELECT * FROM test;

运行结果(1 行数据):

在这个事务中,整个 SQL 一共执行了 2 个事务,第一个是插入“关羽”,提交后执行成功,第二个是插入两次“张飞”,这里需要注意的是,我们将 name 设置为了主键,也就是说主键的值是唯一的,那么第二次插入“张飞”时就会产生错误,然后执行 ROLLBACK 相当于对事务进行了回滚,所以我们看到最终结果只有一行数据,也就是第一个事务执行之后的结果,即“关羽”。

CREATE TABLE test(name varchar(255), PRIMARY KEY (name)) ENGINE=InnoDB;
BEGIN;
INSERT INTO test SELECT '关羽';
COMMIT;
INSERT INTO test SELECT '张飞';
INSERT INTO test SELECT '张飞';
ROLLBACK;
SELECT * FROM test;

运行结果(2 行数据):

你能看到这次数据是 2 行,上一次操作我把两次插入“张飞”放到一个事务里,而这次操作它们不在同一个事务里,那么对于 MySQL 来说,默认情况下这实际上就是两个事务,因为在 autocommit=1 的情况下,MySQL 会进行隐式事务,也就是自动提交,因此在进行第一次插入“张飞”后,数据表里就存在了两行数据,而第二次插入“张飞”就会报错:1062 - Duplicate entry '张飞' for key 'PRIMARY'。最后我们在执行 ROLLBACK 的时候,实际上事务已经自动提交了,就没法进行回滚了。

CREATE TABLE test(name varchar(255), PRIMARY KEY (name)) ENGINE=InnoDB;
SET @@completion_type = 1;
BEGIN;
INSERT INTO test SELECT '关羽';
COMMIT;
INSERT INTO test SELECT '张飞';
INSERT INTO test SELECT '张飞';
ROLLBACK;
SELECT * FROM test;

运行结果(1 行数据):

MySQL 中 completion_type 参数的作用,实际上这个参数有 3 种可能:

  1. completion=0,这是默认情况。也就是说当我们执行 COMMIT 的时候会提交事务,在执行下一个事务时,还需要我们使用 START TRANSACTION 或者 BEGIN 来开启。
  2. completion=1,这种情况下,当我们提交事务后,相当于执行了 COMMIT AND CHAIN,也就是开启一个链式事务,即当我们提交事务之后会开启一个相同隔离级别的事务(隔离级别会在下一节中进行介绍)。
  3. completion=2,这种情况下 COMMIT=COMMIT AND RELEASE,也就是当我们提交后,会自动与服务器断开连接。

在上面这段代码里,我使用了 completion=1,也就是说当我提交之后,相当于在下一行写了一个 START TRANSACTION 或 BEGIN。这时两次插入“张飞”会被认为是在同一个事务之内的操作,那么第二次插入“张飞”就会导致事务失败,而回滚也将这次事务进行了撤销,所以你能看到的结果就只有一个“关羽”。

4,总结

  • 当我们设置 autocommit=0 时,不论是否采用 START TRANSACTION 或者 BEGIN 的方式来开启事务,都需要用 COMMIT 进行提交,让事务生效,使用 ROLLBACK 对事务进行回滚。
  • 当我们设置 autocommit=1 时,每条 SQL 语句都会自动进行提交。不过这时,如果你采用 START TRANSACTION 或者 BEGIN 的方式来显式地开启事务,那么这个事务只有在 COMMIT 时才会生效,在 ROLLBACK 时才会回滚。
  • 我们在做数据库操作的时候,可能会失败,但正是因为有事务的存在,即使在数据库操作失败的情况下,也能保证数据的一致性。同样,多个应用程序访问数据库的时候,事务可以提供隔离,保证事务之间不被干扰。最后,事务一旦提交,结果就会是永久性的,这就意味着,即使系统崩溃了,数据库也可以对数据进行恢复。
  • 事务是数据库区别于文件系统的重要特性之一,当我们有了事务就会让数据库始终保持一致性,同时我们还能通过事务的机制恢复到某个时间点,这样可以保证已提交到数据库的修改不会因为系统崩溃而丢失。

MySQL可以通过 SHOW ENGINES 命令来查看当前 MySQL 支持的存储引擎都有哪些,以及这些存储引擎是否支持事务。

5,初识事务隔离

隔离性是事务的基本特性之一,它可以防止数据库在并发处理时出现数据不一致的情况。最严格的情况下,我们可以采用串行化的方式来执行每一个事务,这就意味着事务之间是相互独立的,不存在并发的情况。然而在实际生产环境下,考虑到随着用户量的增多,会存在大规模并发访问的情况,这就要求数据库有更高的吞吐能力,这个时候串行化的方式就无法满足数据库高并发访问的需求,我们还需要降低数据库的隔离标准,来换取事务之间的并发能力。有时候我们需要牺牲一定的正确性来换取效率的提升,也就是说,我们需要通过设置不同的隔离等级,以便在正确性和效率之间进行平衡。

我们需要了解设定事务的隔离级别都要解决哪些可能存在的问题,也就是事务并发处理时会存在哪些异常情况。SQL-92 标准中已经对 3 种异常情况进行了定义,这些异常情况级别分别为脏读(Dirty Read)、不可重复读(Nnrepeatable Read)和幻读(Phantom Read)。

  • 脏读:读到了其他事务还没有提交的数据。
  • 不可重复读:对某数据进行读取,发现两次读取的结果不同,也就是说没有读到相同的内容。这是因为有其他事务对这个数据同时进行了修改或删除。
  • 幻读:事务 A 根据条件查询得到了 N 条数据,但此时事务 B 更改或者增加了 M 条符合事务 A 查询条件的数据,这样当事务 A 再次进行查询的时候发现会有 N+M 条数据,产生了幻读。

事务隔离的级别有哪些:

解决异常数量从少到多的顺序(比如读未提交可能存在 3 种异常,可串行化则不会存在这些异常)决定了隔离级别的高低,这四种隔离级别从低到高分别是:读未提交(READ UNCOMMITTED )、读已提交(READ COMMITTED)、可重复读(REPEATABLE READ)和可串行化(SERIALIZABLE)。这些隔离级别能解决的异常情况如下表所示:

  • 读未提交,也就是允许读到未提交的数据,这种情况下查询是不会使用锁的,可能会产生脏读、不可重复读、幻读等情况。
  • 读已提交就是只能读到已经提交的内容,可以避免脏读的产生,属于 RDBMS 中常见的默认隔离级别(比如说 Oracle 和 SQL Server),但如果想要避免不可重复读或者幻读,就需要我们在 SQL 查询的时候编写带加锁的 SQL 语句。
  • 可重复读,保证一个事务在相同查询条件下两次查询得到的数据结果是一致的,可以避免不可重复读和脏读,但无法避免幻读。MySQL 默认的隔离级别就是可重复读
  • 可串行化,将事务进行串行化,也就是在一个队列中按照顺序执行,可串行化是最高级别的隔离等级,可以解决事务读取中所有可能出现的异常情况,但是它牺牲了系统的并发性。

即使是不同的 RDBMS 都需要达成对异常问题和隔离级别定义的共识。这就意味着一个隔离级别的实现满足了下面的两个条件:

  • 正确性:只要能满足某一个隔离级别,一定能解决这个隔离级别对应的异常问题。
  • 与实现无关:实际上 RDBMS 种类很多,这就意味着有多少种 RDBMS,就有多少种锁的实现方式,因此它们实现隔离级别的原理可能不同,然而一个好的标准不应该限制其实现的方式。

隔离级别越低,意味着系统吞吐量(并发程度)越大,但同时也意味着出现异常问题的可能性会更大。在实际使用过程中我们往往需要在性能和正确性上进行权衡和取舍,没有完美的解决方案,只有适合与否。

不可重复读 VS 幻读的区别:

  • 不可重复读是同一条记录的内容被修改了,重点在于UPDATE或DELETE
  • 幻读是查询某一个范围的数据行变多了或者少了,重点在于INSERT

所以,SELECT 显示不存在,但是INSERT的时候发现已存在,说明符合条件的数据行发生了变化,也就是幻读的情况,而不可重复读指的是同一条记录的内容被修改了。

三,游标

1,什么是游标?

在数据库中,游标是个重要的概念,它提供了一种灵活的操作方式,可以让我们从数据结果集中每次提取一条数据记录进行操作。游标让 SQL 这种面向集合的语言有了面向过程开发的能力。可以说,游标是面向过程的编程方式,这与面向集合的编程方式有所不同。

2,如何使用游标?

我先创建一个存储过程 calc_hp_max,然后在存储过程中定义游标 cur_hero,使用 FETCH 获取每一行的具体数值,然后赋值给变量 hp,再用变量 hp_sum 做累加求和,最后再输出 hp_sum,代码如下:

当游标溢出时(也就是当游标指向到最后一行数据后继续执行会报的错误),我们可以定义一个 continue 的事件,指定这个事件发生时修改变量 done 的值,以此来判断游标是否已经溢出,同时在循环中我们需要加上对 done 的判断,如果游标的循环已经结束,就需要跳出 read_loop 循环


CREATE PROCEDURE `calc_hp_max`()
BEGIN
       -- 创建接收游标的变量
       DECLARE hp INT;  

       -- 创建总数变量 
       DECLARE hp_sum INT DEFAULT 0;
       -- 创建结束标志变量  
     DECLARE done INT DEFAULT false;
       -- 定义游标     
       DECLARE cur_hero CURSOR FOR SELECT hp_max FROM heros;
       -- 指定游标循环结束时的返回值  
     DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = true;  
       
       OPEN cur_hero;
       read_loop:LOOP 
       FETCH cur_hero INTO hp;
       -- 判断游标的循环是否结束  
       IF done THEN  
                     LEAVE read_loop;
       END IF; 
              
       SET hp_sum = hp_sum + hp;
       END LOOP;
       CLOSE cur_hero;
       SELECT hp_sum;
END

在游标中的循环中,除了使用 LOOP 循环以外,你还可以使用 REPEAT… UNTIL…以及 WHILE 循环。它们同样需要设置 CONTINUE 事件来处理游标溢出的情况。

使用游标可以让我们对 SELECT 结果集中的每一行数据进行相同或者不同的操作,从而很精细化地管理结果集中的每一条数据。

四,Python操作数据库

Python 可以支持非常多的数据库管理系统,比如 MySQL、Oracle、SQL Server 和 PostgreSQL 等。为了实现对这些 DBMS 的统一访问,Python 需要遵守一个规范,这就是 DB API 规范。

我们在使用 Python 对 DBMS 进行操作的时候,需要经过下面的几个步骤:

  1. 引入 API 模块;
  2. 与数据库建立连接;
  3. 执行 SQL 语句;
  4. 关闭数据库连接。

用 Python 操作 MySQL,还有很多种姿势,mysql-connector 只是其中一种,实际上还有另外一种方式,就是采用 ORM 框架。ORM 的英文是 Object Relational Mapping,也就是采用对象关系映射的模式,使用这种模式可以将数据库中各种数据表之间的关系映射到程序中的对象。这种模式可以屏蔽底层的数据库的细节,不需要我们与复杂的 SQL 语句打交道,直接采用操作对象的形式操作就可以。不过如果应用数据实体少,其实没有必要使用 ORM 框架,针对少量对象的管理,自己实现起来也很简单,比如本篇文章中我讲到的采用官方提供的 mysql-connector 驱动的方式来实现 CRUD。引入一个框架的学习成本很高,代码膨胀也很厉害,所以如果是相对简单的操作,完全可以自己动手来实现。

五,基础篇答疑

1,可以理解在 WHERE 条件字段上加索引,但是为什么在 ORDER BY 字段上还要加索引呢?这个时候已经通过 WHERE 条件过滤得到了数据,已经不需要再筛选过滤数据了,只需要根据字段排序就好了。

在 MySQL 中,支持两种排序方式,分别是 FileSort 和 Index 排序。在 Index 排序中,索引可以保证数据的有序性,不需要再进行排序,效率更高。而 FileSort 排序则一般在内存中进行排序,占用 CPU 较多。如果待排结果较大,会产生临时文件 I/O 到磁盘进行排序的情况,效率较低。所以使用 ORDER BY 子句时,应该尽量使用 Index 排序,避免使用 FileSort 排序。当然你可以使用 explain 来查看执行计划,看下优化器是否采用索引进行排序。

  1. SQL 中,可以在 WHERE 子句和 ORDER BY 子句中使用索引,目的是在 WHERE 子句中避免全表扫描,在 ORDER BY 子句避免使用 FileSort 排序。当然,某些情况下全表扫描,或者 FileSort 排序不一定比索引慢。但总的来说,我们还是要避免,以提高查询效率。一般情况下,优化器会帮我们进行更好的选择,当然我们也需要建立合理的索引。
  2. 尽量使用 Index 完成 ORDER BY 排序。如果 WHERE 和 ORDER BY 后面是相同的列就使用单索引列;如果不同就使用联合索引。
  3. 无法使用 Index 时,需要对 FileSort 方式进行调优。

2,ORDER BY 是对分的组排序还是对分组中的记录排序呢?

ORDER BY 就是对记录进行排序。如果你在 ORDER BY 前面用到了 GROUP BY,实际上这是一种分组的聚合方式,已经把一组的数据聚合成为了一条记录,再进行排序的时候,相当于对分的组进行了排序。

3,请问下关于 SELECT 语句内部的执行步骤。

一条完整的 SELECT 语句内部的执行顺序是这样的:

  1. FROM 子句组装数据(包括通过 ON 进行连接);
  2. WHERE 子句进行条件筛选;
  3. GROUP BY 分组 ;
  4. 使用聚集函数进行计算;
  5. HAVING 筛选分组;
  6. 计算所有的表达式;
  7. SELECT 的字段;
  8. ORDER BY 排序;
  9. LIMIT 筛选。

4,不太理解哪种情况下应该使用 EXISTS,哪种情况应该用 IN。选择的标准是看能否使用表的索引吗?

索引是个前提,其实选择与否还是要看表的大小。你可以将选择的标准理解为小表驱动大表。在这种方式下效率是最高的。比如下面这样:

SELECT * FROM A WHERE cc IN (SELECT cc FROM B)
SELECT * FROM A WHERE EXISTS (SELECT cc FROM B WHERE B.cc=A.cc)

当 A 小于 B 时,用 EXISTS。因为 EXISTS 的实现,相当于外表循环,实现的逻辑类似于:

for i in A
     for j in B
         if j.cc == i.cc then ...

当 B 小于 A 时用 IN,因为实现的逻辑类似于:

 for i in B
     for j in A
         if j.cc == i.cc then ...

哪个表小就用哪个表来驱动,A 表小就用 EXISTS,B 表小就用 IN。

关于存储过程

5,在使用存储过程声明变量时,都支持哪些数据类型呢?

不同的 DBMS 对数据类型的定义不同,你需要查询相关的 DBMS 文档。以 MySQL 为例,常见的数据类型可以分成三类,分别是数值类型、字符串类型和日期/时间类型。

6,“IN 参数必须在调用存储过程时指定”的含义是什么?我查询了 MySQL 的存储过程定义,可以不包含 IN 参数。当存储过程的定义语句里有 IN 参数时,存储过程的语句中必须用到这个参数吗?

如果存储过程定义了 IN 参数,就需要在调用的时候传入。当然在定义存储过程的时候,如果不指定参数类型,就默认是 IN 类型的参数。因为 IN 参数在存储过程中是默认值,可以省略不写。

在存储过程中的语句里,不一定要用到 IN 参数,只是在调用的时候需要传入这个。另外 IN 参数在存储过程中进行了修改,也不会进行返回的。如果想要返回参数,需要使用 OUT,或者 INOUT 参数类型。

关于事务处理

7,如果INSERT INTO test SELECT '关羽';之后没有执行 COMMIT,结果应该是空。但是我执行出来的结果是'关羽',为什么 ROLLBACK 没有全部回滚?

 CREATE TABLE test(name varchar(255), PRIMARY KEY (name)) ENGINE=InnoDB;
 BEGIN;
 INSERT INTO test SELECT '关羽';
 BEGIN;
 INSERT INTO test SELECT '张飞';
 INSERT INTO test SELECT '张飞';
 ROLLBACK;
 SELECT * FROM test;

先解释下连续 BEGIN 的情况。在 MySQL 中 BEGIN 用于开启事务,如果是连续 BEGIN,当开启了第一个事务,还没有进行 COMMIT 提交时,会直接进行第二个事务的 BEGIN,这时数据库会隐式地 COMMIT 第一个事务,然后再进入到第二个事务。

为什么 ROLLBACK 没有全部回滚呢?因为 ROLLBACK 是针对当前事务的,在 BEGIN 之后已经开启了第二个事务,当遇到 ROLLBACK 的时候,第二个事务都进行了回滚,也就得到了第一个事务执行之后的结果即“关羽”。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值