第十章 数据库专题
10.1. 如何获取当前数据库版本?
答:运行 SQL SERVER 服务管理器,在任务栏小托盘处,右键单击管理器图标,选“关于”,在弹出的窗口中,对照上面的信息便可获取。
10.2. InnoDB 题
一张自增表里面总共有 7 条数据,删除了最后 2 条数据,重启 MySQL 数据库,又插入了一条数据,此时 id 是几?
答:表类型如果是 MyISAM ,那 id 就是 8。
表类型如果是 InnoDB,那 id 就是 6。
10.3. 序列的作用
Oracle 使用序列来生成唯一编号,用来处理一个表中自增字段。 Oracle 序列是原子对象,并且是一致的。也就是说,一旦您访问一个序列号,Oracle 将在处理下一个请求之前自动递增下一个编号,从而确保不会出现重复值。
10.4. 触发器的作用:
触发器是一中特殊的存储过程,主要是通过事件来触发而被执行的。它可以强化约束,来维护数据的完整性和一致性,可以跟踪数据库内的操作从而不允许未经许可的更新和变化。可以联级运算。如,某表上的触发器上包含对另一个表的数据操作,而该操作又会导致该表触发器被触发。
10.5. 什么是存储过程?用什么来调用?
存储过程是一个预编译的 SQL 语句,优点是允许模块化的设计,就是说只需创建一次,以后在该程序中就可以调用多次。如果某次操作需要执行多次 SQL,使用存储过程比单纯SQL 语句执行要快。
调用:
1)可以用一个命令对象来调用存储过程。
2)可以供外部程序调用,比如:java 程序。
10.6. 存储过程的优缺点?
优点:
存储过程是预编译过的,执行效率高。
存储过程的代码直接存放于数据库中,通过存储过程名直接调用,减少网络通讯。
安全性高,执行存储过程需要有一定权限的用户。
存储过程可以重复使用,可减少数据库开发人员的工作量。
缺点:
移植性差
10.7. 存储过程与函数的区别
存储过程 函数用于在数据库中完成特定的操作或者任务(如插入、删除等)用于特定的数据(如选择)程序头部声明用 procedure 程序头部声明用 function程序头部声明时不需描述返回类型 程序头部声明时要描述返回类型,而且PL/SQL 块中至少要包括一个有效的return 语句可以使用 in/out/in out 三种模式的参数可以使用 in/out/in out 三种模式的参数可作为一个独立的 PL/SQL 语句来执行 不能独立执行,必须作为表达式的一部分调用可以通过 out/in out 返回零个或多个值通过 return 语句返回一个值,且改值要与声明部分一致,也可以是通过 out类型的参数带出的变量SQL 语句(DML或 SELECT)中不可调用存储过程SQL 语句(DML 或 SELECT)中可以调用函数
10.8. 索引的作用?和它的优点缺点是什么?
索引:索引就一种特殊的查询表,数据库的搜索可以利用它加速对数据的检索。
优点:它很类似与现实生活中书的目录,不需要查询整本书内容就可以找到想要的数据。索引可以是唯一的,创建索引允许指定单个列或者是多个列。
缺点:是它减慢了数据录入的速度,同时也增加了数据库的尺寸大小。
10.9. 什么样的字段适合建索引
唯一、不为空、经常被查询的字段
10.10. 索引类型有哪些?
逻辑上:
1.Single column 单行索引
2.Concatenated 多行索
3.Unique 唯一索引
4.NonUnique 非唯一索引
5.Function-based 函数索引
6.Domain 域索引 物理上:
7.Partitioned 分区索引
8.NonPartitioned 非分区索引
9.tree :
Normal 正常型 B 树
Rever Key 反转型 B 树 Bitmap 位图索引
10.11. 什么是数据库事务?
答案:
单个逻辑单元执行的一系列操作,这些操作要么全做要么全不做,是不可分割的.事务的开始和结束用户是可以控制的,如果没控制则由数据库默认的划分事务.事务具有以下性质:
(1) 原子性:
指一个事务要么全执行,要么全不执行.也就是说一个事务不可能执行到一半就停止了.
(2) 一致性:
指事务的运行并不改变数据库中的一致性.比如 a+b=10;a 改变了,b 也应该随之改变.
(3) 独立性:- 63 -
两个以上的事务不会出现交替运行的状态,因为这样可能导致数据的不一致
(4) 持久性:
事务运行成功之后数据库的更新是永久的
10.12. SQL 中分别有几种事务?各有什么特点?:
答:4 种事务:
自动提交事务
隐式事务特点
显示事务
分布式事务
自动提交事务特点:每条单独语句都是一个事务。
隐式事务特点:前一个事务完成时新事务隐式启动,每个事务仍以 COMMIT 或ROLLBACK 语句、显示结束。
显示事务特点:每个事务均已 BEGIN TRANSACTION 语句显示开始,以 COMMIT 或ROLLBACK 语句显示结束。
分布式事务特点:跨越多个服务器的事务
10.13. 什么是回滚?
为了保证在应用程序、数据库或系统出现错误后,数据库能够被还原,以保证数据库的完整性 ,所以需要进行回滚。回滚(rollback)就是在事务提交之前将数据库数据恢复到事务修改之前数据库数据状态。
10.14. 什么是事务?什么是锁?
事务:
事务就是被绑定在一起作为一个逻辑工作单元的 SQL 语句分组,如果任何一个语句操作失败那么整个操作就被失败,以后操作就会回滚到操作前状态,或者是上有个节点。为了确保要么执行,要么不执行,就可以使用事务。要将有组语句作为事务考虑,就需要通过ACID 测试,即原子性,一致性,隔离性和持久性。
锁:
在所有的 DBMS 中,锁是实现事务的关键,锁可以保证事务的完整性和并发性。与现
实生活中锁一样,它可以使某些数据的拥有者,在某段时间内不能使用某些数据或数据结构。
当然锁还分级别的。- 64 -
10.15. 什么是封锁?封锁的基本类型有哪几种?含义是什么?
封锁:
所谓封锁就是当一个事务在对某个数据对象(可以是数据项、记录、数据集、以至整个数据库)进行操作之前,必须获得相应的锁,以保证数据操作的正确性和一致性。
基本的封锁类型有两种:排它锁和共享锁
(1)排它锁:排它锁又称写锁,简称为 X 锁,其采用的原理是禁止并发操作。
(2)共享锁:共享锁又称读锁,,简称为 S 锁,其采用的原理是允许其他用户对同一数
据对象进行查询,但不能对该数据对象进行修改。
10.16. sql 数据库里锁是什么以及分类介绍
目前的 C/S,B/S 结构都是多用户访问数据库,每个时间点会有成千上万个 user 来访问DB,其中也会同时存取同一份数据,会造成数据的不一致性或者读脏数据,数据库必须有锁的机制来确保数据的完整和一致性
6 种锁的类型:
(1) 共享锁:
共享锁用于所有的只读数据操作.
(2) 修改锁:
修改锁在修改操作的初始化阶段用来锁定可能要被修改的资源,这样可以避免使用共享
锁造成的死锁现象
(3) 独占锁:
独占锁是为修改数据而保留的。它所锁定的资源,其他事务不能读取也不能修改。独占
锁不能和其他锁兼容。
(4) 架构锁
结构锁分为结构修改锁(Sch-M)和结构稳定锁(Sch-S)。执行表定义语言操作时,
SQLServer 采用 Sch-M 锁,编译查询时,SQLServer 采用 Sch-S 锁。
(5) 意向锁
意向锁说明 SQL Server 有在资源的低层获得共享锁或独占锁的意向。
(6) 批量修改锁
批量复制数据时使用批量修改锁
10.17. 什么是死锁?产生死锁的必要条件。
1)在同时处于等待状态的两个或多个事务中,每个事务都在等待其中另一个事务解除封锁,它才能继续执行下去,结果造成任何一个事务都无法继续执行,这种状态称为死锁.
2)发生死锁的必要条件有以下四条:
(1)互斥条件:一个数据对象一次只能被一个事务所使用,即对数据的封锁采用排它式;
(2)不可抢占条件:一个数据对象只能被占有它的事务所释放,而不能被别的事务强行抢占
(3)互斥条件:一个数据对象一次只能被一个事务所使用,即对数据的封锁用排它式;
(4)不可抢占条件:一个数据对象只能被占有它的事务所释放,而不能被别的事务强行抢占
10.18. 什么叫视图?游标是什么?
视图:
是一种虚拟的表,具有和物理表相同的功能。可以对视图进行增,改,查,操作,试图通常是有一个表或者多个表的行或列的子集。对视图的修改会影响基本表。它使得我们获取数据更容易,相比多表查询。
游标:
是对查询出来的结果集作为一个单元来有效的处理。游标可以定在该单元中的特定行,从结果集的当前行检索一行或多行。可以对结果集当前行做修改。一般不使用游标,但是需要逐条处理数据的时候,游标显得十分重要。
10.19. 使用游标的基本步骤有哪些?
- 声明游标
- 打开游标
- 读取游标中的数据
- 关闭游标
- 释放游标
10.20. 视图的优缺点
优点:
对数据库的访问,因为视图可以有选择性的选取数据库里的一部分。用户通过简单的查询可以从复杂查询中得到结果。维护数据的独立性,试图可从多个表检索数据。对于相同的数据可产生不同的视图。
缺点:
性能:查询视图时,必须把视图的查询转化成对基本表的查询,如果这个视图是由一个复杂的多表查询所定义,那么,那么就无法更改数据。
10.21. 列举几种表连接方式,有什么区别?
内连接、自连接、外连接(左、右、全)、交叉连接
内连接:只有两个元素表相匹配的才能在结果集中显示。
外连接:左外连接:左边为驱动表,驱动表的数据全部显示,匹配表的不匹配的不会显示。
右外连接: 右边为驱动表,驱动表的数据全部显示,匹配表的不匹配的不会显示。
全外连接:连接的表中不匹配的数据全部会显示出来。
交叉连接: 笛卡尔效应,显示的结果是链接表数的乘积。
10.22. 主键和外键的区别?
1.主键在本表中是唯一的、不可唯空的。
2.外键可以重复可以唯空。
3.外键和另一张表的主键关联,不能创建对应表中不存在的外键。
10.23. 在数据库中查询语句速度很慢,如何优化?
- 建索引
- 减少表之间的关联
- 优化 sql,尽量让 sql 很快定位数据,不要让 sql 做全表查询,应该走索引,把数据 量大的表排在前面
- 简化查询字段,没用的字段不要,已经对返回结果的控制,尽量返回少量数据
- 尽量用 PreparedStatement 来查询,不要用 Statement
10.24. 数据库三范式是什么?
(1)第一范式(1NF):
所谓第一范式(1NF)是指在关系模型中,对于添加的一个规范要求,所有的域都应该
是原子性的,即数据库表的每一列都是不可分割的原子数据项,而不能是集合,数组,记录
等非原子数据项。
(2)第二范式(2NF):- 67 -
在 1NF 的基础上,非码属性必须完全依赖于候选码(在 1NF 基础上消除非主属性对主
码的部分函数依赖)。
第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)
必须先满足第一范式(1NF)。第二范式(2NF)要求数据库表中的每个实例或记录必须可
以被唯一地区分。选取一个能区分每个实体的属性或属性组,作为实体的唯一标识。
(3)第三范式(3NF):
第三范式(3NF)是第二范式(2NF)的一个子集,即满足第三范式(3NF)必须满足
第二范式(2NF)。简而言之,第三范式(3NF)要求一个关系中不包含已在其它关系已包
含的非主关键字信息。
10.25. union 和 union all 有什么不同?
Union:
UNION 在进行表链接后会筛选掉重复的记录,所以在表链接后会对所产生的结果集进
行排序运算,删除重复的记录再返回结果。实际大部分应用中是不会产生重复的记录,最常
见的是过程表与历史表 UNION。
union all:
UNION ALL 只是简单的将两个结果合并后就返回。这样,如果返回的两个结果集中有
重复的数据,那么返回的结果集就会包含重复的数据了。 从效率上说,UNION ALL 要比
UNION 快很多,所以,如果可以确认合并的两个结果集中不包含重复的数据的话,那么就
使用 UNION ALL。
10.26. CHAR 和 VARCHAR 的区别是什么?
答:
(1)CHAR 的长度是固定的,而 VARCHAR 的长度是可以变化的。
(2)CHAR 的效率比 VARCHAR 的效率稍高。
(3)目前 VARCHAR 是 VARCHAR 的同义词。
10.27. Varchar2 和 varchar 有什么区别?
Char 的长度是固定的,而 varchar2 的长度是可以变化的,比如,存储字符串“abc”
对于 char(20),表示你存储的字符将占 20 个字节,包含 17 个空,而同样的 varchar2(20)只占了 3 个字节,20 只是最大值,当你存储的字符小于 20 时,按实际长度存储。 char 的效率要被 varchar2 的效率高。 目前 varchar 是 varchar2 的同义词,工业标准的 varchar类型可以存储空字符串,但是 oracle 不能这样做,尽管它保留以后这样做的权利。Oracle自己开发了一个数据类型 varchar2,这个类型不是一个标准的 varchar,他将在数据库中varchar 列可以存储空字符串的特性改为存储 null 值,如果你想有向后兼容的能力,oracle建议使用 varchar2 而不是 varchar
10.28. float 和 double 的区别是什么?
(1)范围
float 和 double 的范围是由指数的位数来决定的。
(2)精度
float 和 double 的精度是由尾数的位数来决定的。浮点数在内存中是按科学计数法来
存储的,其整数部分始终是一个隐含着的“1”,由于它是不变的,故不能对精度造成影响。
10.29. Oracle 和 Mysql 的区别?
1)库函数不同。
2)Oracle 是用表空间来管理的,Mysql 不是。
3)显示当前所有的表、用户、改变连接用户、显示当前连接用户、执行外部脚本的语句的不同。
4)分页查询时候时候,mysql 用 limit oracle 用 rownum
10.30. sql 语句应该考虑哪些安全性?
(1)防止 sql 注入,对特殊字符进行转义,过滤或者使用预编译的 sql 语句绑定变量。
(2)最小权限原则,特别是不要用 root 账户,为不同的类型的动作或者组建使用不同的账户。
(3)当 sql 运行出错时,不要把数据库返回的错误信息全部显示给用户,以防止泄漏服务器和数据库相关信息。
10.31. 什么是内存泄漏?
答:一般我们所说的内存泄漏指的是堆内存的泄漏。堆内存是程序从堆中为其分配的,大小任意的,使用完后要显示释放内存。当应用程序用关键字 new 等创建对象时,就从堆中为它分配一块内存,使用完后程序调用 free 或者 delete 释放该内存,否则就说该内存就不能被使用,我们就说该内存被泄漏了。
10.32. Delete、truncate、drop 都是删除语句,它们有什么分别?
- delete 属于 DML 语句,删除数据,保留表结构,需要 commit,可以回滚,如果数据量大,很慢。
- truncate 属于 DDL 语句,删除所有数据,保留表结构,自动 commit,不可以回滚,一次全部删除所有数据,速度相对较快。
- Drop 属于 DDL 语句,删除数据和表结构,不需要 commit,删除速度最快。
10.33. order by 和 groupby 的区别
order by: 排序查询、asc 升序、desc 降序
group by:分组查询、having 只能用于 groupby 子句、作用于组内,having 条件子句可以直接跟函数表达式。使用 group by 子句的查询语句需要使用聚合函数。
10.34. 何谓悲观锁与乐观锁
乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点,不能不以场景而定说一种人好于另外一种人。
悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资 源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁 等,读锁,写锁等,都是在做操作之前先上锁。Java中 synchronized 和 ReentrantLock 等独占锁就是悲观锁思想的实现。
乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以 使用版本号机制和CAS 算法实现。乐观锁适用于多读的应用类型,这样可以提 高吞吐量,像数据库提供的类似于 write_condition 机制,其实都是提供的乐 观锁。在 Java 中java.util.concurrent.atomic 包下面的原子变量类就是使用了 乐观锁的一种实现方式 CAS实现的。
两种锁的使用场景
从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一 种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的 时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的 情况,一般会经常产生冲突,这就会导致上层应用会不断的进行 retry,这样反 倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
乐观锁常见的两种实现方式
乐观锁一般会使用版本号机制或 CAS 算法实现。
版本号机制
一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次 数,当数据被修改时,version 值会加一。当线程 A 要更新数据值时,在读取数 据的同时也会读取version 值,在提交更新时,若刚才读取到的 version 值为当 前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。
举一个简单的例子:假设数据库中帐户信息表中有一个 version 字段,当前值 为 1 ;
而当前帐户余额字段( balance )为 $100 。
- 操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 $50
( $100-$50 )。
- 在操作员 A 操作的过程中,操作员 B 也读入此用户信息( version=1 ),并从
其帐户余额中扣除 $20 ( $100-$20 )。
- 操作员 A 完成了修改工作,将数据版本号加一( version=2 ),连同帐户扣除后余额( balance=$50 ),提交至数据库更新,此时由于提 交数据版本大于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。
- 操作员 B 完成了操作,也将版本号加一( version=2 )试图向数据库 提交数据( balance=$80 ),但此时比对数据库记录版本时发现,操 作员 B 提交的数据版本号为 2 ,数据库记录当前版本也为 2 ,不满 足 “ 提交版本必须大于记录当前版本才能执行更新 “ 的乐观锁策略, 因此,操作员 B 的提交被驳回。这样,就避免了操作员 B 用基于 version=1 的旧数据修改的结果覆盖操作员 A 的操作结果的可能。
- CAS 算法
即 compare and swap(比较与交换),是一种有名的无锁算法。无锁编程, 即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的 情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS 算法涉及到三个操作数需要读写的内存值 V进行比较的值 A拟写入的新值 B当且仅当 V 的值等于 A 时,CAS 通过原子方式用新值 B 来更新 V 的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。
乐观锁的缺点
ABA 问题是乐观锁一个常见的问题
1 ABA 问题
如果一个变量 V 初次读取的时候是 A 值,并且在准备赋值的时候检查到它仍然 是 A 值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回 A,那 CAS 操作就 会误认为它从来没有被修改过。这个问题被称为 CAS 操作的 "ABA"问题。
JDK 1.5 以后的 AtomicStampedReference 类就提供了此种能力,其中的compareAndSet 方法就是首先检查当前引用是否等于预期引用,并且当前标志是 否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为 给定的更新值。
2 循环时间长开销大
自旋 CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给 CPU 带来非常大的执行开销。 如果 JVM 能支持处理器提供的 pause 指令那么 效率会有一定的提升,pause 指令有两个作用,第一它可以延迟流水线执行指 令(de-pipeline),使 CPU 不
会消耗过多的执行资源,延迟的时间取决于具体 实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时 候因内存顺序冲突(memory order violation)而引起 CPU 流水线被清空 (CPU pipeline flush),从而提高 CPU 的执行效率。
3 只能保证一个共享变量的原子操作
CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是 从 JDK1.5 开始,提供了 AtomicReference 类来保证引用对象之间的原子性,你 可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利 用 AtomicReference类把多个共享变量合并成一个共享变量来操作。CAS 与 synchronized 的使用情景
简单的来说 CAS 适用于写比较少的情况下(多读场景,冲突一般较少),synchronized适用于写比较多的情况下(多写场景,冲突一般较多)
1.对于资源竞争较少(线程冲突较轻)的情况,使用 synchronized 同步锁 进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗 cpu 资源;而 CAS 基于硬件实现,不需要进入内核,不需要切换线程, 操作自旋几率较少,因此可以获得更高的性能。
- 对于资源竞争严重(线程冲突严重)的情况,CAS 自旋的概率会比较 大,从而浪费更多的 CPU 资源,效率低于 synchronized。
补充: Java 并发编程这个领域中 synchronized 关键字一直都是元老级的角 色,很久之前很多人都会称它为 “重量级锁” 。但是,在 JavaSE 1.6 之后进行了 主要包括为了减少获得锁和释放锁带来的性能消耗而引入的 偏向锁 和 轻量级 锁 以及其它各种优化之后变得在某些情况下并不是那