2020年Python最新面试题(五):数据库相关(一)

1. 数据库基础理论

1.1 数据库的常见分类有哪些?

数据库可以按照存储模型、关系型/非关系型来进行分类,其分类如下图所示:

在这里插入图片描述
(1) 网络数据库、层次数据库

数据库若按照使用的数据存储模型来划分,则可以把数据库分为网状数据库 (Network Database)、关系型数据库(Relational Database) 和 层次数据库 (Hierarchical Database)。其中,商业中使用最广泛的数据库主要是关系型数据库,例如,Oracle、MySQL、DB2、SQL Server 等。

网状数据库 (Network Database) 是指处理以记录类型为结点的网状数据模型的数据库,处理方法是将网状结构分解成若干棵二级树结构,称为系,其代表是 DBTG (DataBase Task Group,数据库任务组)系统。系类型是两个或两个以上的记录类型之间联系的一种描述。在一个系类型中,有一个记录类型处于主导地位,称为系主记录类型,其他称为成员记录类型。系主和成员之间的联系是一对多的关系。1969年美国的CODASYL 组织提出了一份 DBTG 报告,此后,根据 DBTG 报告实现的系统一般称为 DBTG 系统。现有的网状数据库系统大都是采用 DBTG方案。DBTG 系统是典型的三级结构体系:子模式、模式、存储模式。相应的数据定义语言分别称为子模式定义语言 (SubSchema Data Definition Language,SSDDL),模式定义语言 (Schema Data Definition Language,SDDL),设备介质控制语言 (Device Medium Control Language,DMCL),另外,还有数据操纵语言(Data Manipulation Language,DML)。

层次数据库(Hierarchical Database) 也叫树状数据库,它是将数据组织成有向有序的树结构,并用 一对多 的关系联结不同层次的数据库。最著名最典型的层次数据库是 IBM 公司的 IMS (Information Management System) 数据库。IMS 是 IBM 公司研制的最早的大型数据库管理系统,其数据库模式是多个物理数据库记录型 (Physical Data Base Record,PDBR) 的集合。每个 PDBR 对应层次数据模型的一个层次模式。各个用户所需数据的逻辑结构称为外模式,每个外模式是一组逻辑数据库记录型(Logical Data Base Record,LDBR)的集合。LDBR 是应用程序所需的局部逻辑结构。

(2) 关系型数据库

RDBMS (Relational Database Management System,关系型数据库管理系统) 是E.F.Codd 博士在其发表的论文《大规模共享数据银行的关系型模型》(Communications of the ACM 杂志1970 年 6月刊) 基础上设计出来的。关系型数据库是将数据组织为相关的行和列的系统,而管理关系型数据库的计算机软件就是 RDBMS。它通过数据、关系和对数据的约束三者组成的数据模型来存放和管理数据。自关系型数据库管理系统被提出以来,RDBMS 获得了长足的发展,许多企业的在线交易处理系统、内部财务系统、客户管理系统等大多采用了 RDBMS。

关系型数据库,顾名思义是建立在关系模型基础上的数据库,借助于集合代数等数学概念和方法来处理数据库中的数据。现实世界中的各种实体以及实体之间的各种联系均用关系模型来表示。结构化查
询语言(Structured Query Language,SQL)就是一种基于关系型数据库的语言,这种语言执行对关系型数据库中数据的检索和操作。关系模型由关系数据结构、关系操作集合和关系完整性约束三部分组成。截至2017年,业界普遍使用的关系型数据库管理系统产品有 Oracle、MySQL、 DB2 以及 SQL Server 等。若按照大小来分类的话,则关系型数据库可以简单分为如下几类:

小型数据库:Access、foxbase、SQLite。
中型数据库:MySQL、SQL Server、Informix。
大型数据库:Oracle、DB2。

扫描二维码关注公众号,回复: 12121260 查看本文章

RDBMS 的特点如下所示:① 数据以表格的形式出现。② 每一行存储着一条单独的记录。③ 每个列作为一条记录的一个属性而存在。④ 许多的行和列组成一张表。⑤ 若干的表组成数据库。

(3) 内存数据库

内存数据库,顾名思义就是将数据放在内存中直接操作的数据库。相对于磁盘,内存的数据读写速度要高出几个数量级,将数据保存在内存中相比从磁盘上访问能够极大地提高应用的性能,典型的内存数据库有 SQLite 和 TimesTen。SAP 公司专门开发了一款大型的内存数据库 HANA,并且在逐步占领市场,而传统的数据库巨头 Oracle 公司开发的 TimesTen 也是一款内存数据库。可以预见,内存数据库将会是未来的一个发展趋势。

(4) Oracle、MySQL、SQL Server

Oracle 数据库,又名 Oracle RDBMS,或简称 Oracle,是甲骨文公司的一款关系型数据库管理系统。它是数据厍领域一直处于领先地位的产品。可以说 Oracle 数据库系统是目前世界上流行的关系型数据库管理系统,系统可移植性好、使用方便、功能强大,适用于各类大、中、小及微机环境。它是一种高效率、可靠性好、适应高吞吐量的数据库解决方案。

MySQL 是一个关系型数据库管理系统,由瑞典 MySQL AB 公司开发,目前属于 Oracle 公司。MySQL 是最流行的关系型数据厍管理系统,在 Web 应用方面,MySQL 是最好的 RDBMS (Relational Database Management System,关系型数据库管理系统) 应用软件之一,广泛地应用于互联网行业。

SQL Server (Microsoft SQL Server,MS Server) 是由Microsoft 开发和推广的关系型数据库管理系统 (DBMS),它最初是由 Microsoft、Sybase 和 Ashton-Tate三家公司共同开发的,并于1988 年推出了第一个 OS/2 版本。SQL Server 是一个全面的数据库平台,使用集成的商业智能(Business Intelligence, BI) 工具提供了企业级的数据管理。SQL Server数据库引擎为关系型数据和结构化数据提供了更安全可靠的存储功能,使用户可以构建和管理用于业务的高可用和高性能的数据应用程序。SQL Server 近年来不断更新版本,目前最新的版本是 SQL Server 2016,并且微软正在研发基于 Linux 版本的 SQL Server,可见 SQL Server 在关系型数据库中也占有一席之地。下表介绍了常见的关系型数据库的特点及其适用场景:

在这里插入图片描述
(5) 非关系型数据库

NoSQL (Not Only SQL),泛指非关系型的数据库,即 不仅仅是SQL。随着 Web2.0 的兴起,传统的关系型数据库在应付Web2.0 网站,特别是超大规模和高并发的 SNS (Social Network Site,社交网) 类型的 Web2.0 纯动态网站时已经显得力不从心,暴露了很多难以克服的问题,而非关系型数据库则由于其本身的特点得到了非常迅速的发展。NoSQL 数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。NoSQL 的拥护者们提倡运用非关系型的数据存储,相对于铺天盖地的关系型数据库运用,这一概念无疑是一种全新的思维的注入。

NoSQL 数据库大约有四大分类:键值 (Key-Value) 数据库、列存储数据库、文档型数据库和图形 (Graph) 数据库。

① 对于键值 (Key-Value) 数据库,主要会使用到一个哈希表,这个表中有一个特定的键和一个指向特定数据的指针。Key-Value 模型对于信息系统来说,其优势在于简单、易部署,但是如果只对部分值进行查询或更新,那么键值数据库就显得效率低下了。常见的键值数据库有:Redis、LevelDB、
RocksDB、Riak KV、Oracle Berkeley DB、Hazelcast、Ehcache、Memcached 等。

② 对于列存储数据库,通常是用来应对分布式存储的海量数据,键仍然存在,但是它们的特点是键指向了多个列。常见的列存储数据库有:HBase、 Cassandra、 Accumulo、HyperTable等。

③ 对于文档型数据库,其灵感来自于 Lotus Notes 办公软件,而且它与第一种键值存储类似。这种类型的数据模型是版本化的文档,半结构化的文档以特定的格式存储,例如 JSON。文档型数据库可以看作是键值数据库的升级版,允许它们之间嵌套键值,而且文档型数据库比键值数据库的查询效率更高。常见的文档型数据库有:CouchDB、MongoDB 等。国内也有文档型数据库 SequoiaDB,该数据库已经开源。如果说 Oracle 是关系型数据库的王者,那么 MongoDB 可以说是非关系型数据库的霸主。MongoDB 是一个基于分布式文件存储的数据库,它由 C++ 语言编写,旨在为 Web 应用提供可扩展的高性能数据存储解决方案。它支持的数据结构非常松散,因此,可以存储比较复杂的数据类型。MongoDB 最大的特点是支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系型数据库单表查询的绝大部分功能,而且还支持对数据建立索引。下表介绍了常见的非关系型数据库的优缺点及其应用场景:

在这里插入图片描述
下表总结了 MongoDB、Riak KV、Hypertable 和 HBase 这四个产品的主要特性:

在这里插入图片描述
④ 对于图形 (Graph) 数据库。它与其他行列以及刚性结构的 SQL 数据库不同,它是使用灵活的图形模型,并且能够扩展到多台服务器上。NoSQL 数据库没有标准的查询语言 (SQL),因此,进行数据库查询需要制定数据模型。许多 NoSQL 数据库都有 REST 式的数据接口或者查询API。常见的图形数据库有:Neo4j、InfoGrid、InfiniteGraph、Titan、Giraph 等。

(6) 行存储和列存储

将表放入存储系统中的方法有两种,行存储 (Row Storage) 和 列存储 (Column Storage),绝大部分数据库是采用行存储的。行存储法是将各行放入连续的物理位置,这很像传统的记录和文件系统,然后由数据库引擎根据每个查询提取需要的列。列存储法是将数据按照列存储到数据库中,与行存储类似。列存储是相对于传统关系型数据库的行存储来说的,简单来说两者的区别就是如何组织表,列存储将所有记录中相同字段的数据聚合存储,而行存储将每条记录的所有字段的数据聚合存储。Sybase 在 2004 年左右就推出了列存储的 Sybase IQ 数据库系统,主要用于在线分析、数据挖掘等查询密集型应用。

列存储不同于传统的关系型数据库,其数据在表中是按行存储的,列方式所带来的重要好处之一就是,由于查询中的选择规则是通过列来定义的,因此整个数据库是自动索引化的。按列存储每个字段的数据聚集存储,在查询时,只需要少数几个字段的时候,能大大减少读取的数据量,一个字段的数据聚集存储,那就更容易为这种聚集存储设计更好的压缩或解压算法。

应用行存储的数据库系统称为行式数据库,同理,应用列存储的数据库系统称为列式数据库。随着列式数据库的发展,传统的行式数据库加入了列式存储的支持,形成具有两种存储方式的数据库系统。

传统的关系型数据库,如 Oracle、DB2、MySQL、SQL Server 等采用行式存储法,当然传统的关系型数据库也在不断发展中。随着 Oracle 12c 推出了 In Memory 组件,使得 Oracle 数据库具有了双模式数据存放方式,从而能够实现对混合类型应用的支持:传统的以行形式保存的数据满足 OLTP 应用,列形式保存的数据满足以查询为主的 OLAP 应用。新兴的 Hbase、HP Vertica、EMC Greenplum 等分布式数据库采用列存储,当然这些数据库也有对行式存储的支持例如 HP Vertica。随着传统关系型数据库与新兴的分布式数据库不断地发展,列式存储与行式存储会不断融合,数据库系统会呈现双模式数据存放方式,这也是商业竞争的需要。

由于设计上的不同,列式数据库在并行查询处理和压缩上更有优势。而且数据是以列为单元存储,完全不用考虑数据建模或者说建模更简单了。要查询计算哪些列上的数据,直接读取列就行。没有万能的数据库,列式数据库也并非万能,只不过给 DBA 提供了更多的选择,DBA 需根据自己的应用场景自行选择。

1.2 事务的概念及其4个特性是什么?

事务的概念:

所谓事务,是指一组相互依赖的操作单元的集合,用来保证对数据库的正确修改,保持数据的完整性,如果一个事务的某个单元操作失败,将取消本次事务的全部操作。例如,银行交易、股票交易和网上购物等,都需要利用事务来控制数据的完整性,比如将A账户的资金转入B账户,在A中扣除成功,在B中添加失败,导致数据失去平衡,事务将回滚到原始状态,即A中没少,B中没多。数据库事务必须具备以下特征(简称ACID):

(1) 原子性 (Atomicity):每个事务是一个不可分割的整体,只有所有的操作单元执行成功,整个事务才成功;否则此次事务就失败,所有执行成功的操作单元必须撤消,数据库回到此次事务之前的状态。

(2) 一致性 (Consistency):在执行一次事务后,关系数据的完整性和业务逻辑的一致性不能被破坏。例如A与B转帐结束后,他们的资金总额是不能改变的。

(3) 隔离性 (Isolation):在并发环境中,一个事务所做的修改必须与其它事务所做的修改相隔离。例如一个事务查看的数据必须是其它并发事务修改之前或修改完毕的数据,不能是修改中的数据。

(4) 持久性 (Durability):事务结束后,对数据的修改是永久保存的,即使系统故障导致重起数据库系统,数据依然是修改后的状态。

事务机制的必要性:

银行应用是解释事务必要性的一个经典例子。假设一个银行的数据库中,有一张账户表 (tb_account),保存着两张借记卡账户A和B,并且要求这两张借记卡账户都不能透支 (即两个账户的余额不能小于零) 。

【例1】 实现从借记卡账户A向B转账700元,成功后再从A向B转账500元。具体步骤如下。

(1) 创建银行的数据库 db_bank,并且选择该数据库为当前默认数据库,具体代码如下:

在这里插入图片描述
(2) 在数据库db_bank中,创建一个名称为tb_account的数据表,具体代码如下:

CREATE TABLE tb_account(
  id int(10) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
  name varchar(30),
  balance FLOAT(8,2) unsigned DEFAULT 0
);

说明: 要想实现账户余额不能透支,可以将余额字段设置为无符号数,也可以通过定义CHECK约束实现。
本实例中采用设置为无符号数实现,这种方法比较简单。

(3) 向tb_account数据表插入两条记录 (账户初始数据),分别为创建A账户,并存储1000元;创建B账户,存储0元,具体代码如下:

INSERT INTO tb_account (name,balance)VALUES
('A',1000),
('B',0);

(4) 查询插入后的结果,具体代码如下:

SELECT * FROM tb_account;

执行结果如下图所示。

在这里插入图片描述
从上图中可以看出,账户A对应的id为1;账户B对应的id为2。在后面转账过程中将使用账户ID (1和2) 代替A和B账户。

(5) 创建模拟转账操作的存储过程。在该存储过程中,实现将一个账户的指定金额,添加到另一个账户中,具体代码如下:

DELIMITER //
CREATE PROCEDURE proc_transfer (IN id_from INT,IN id_to INT,IN money int)
READS SQL DATA
BEGIN
UPDATE tb_account SET balance=balance+money WHERE id=id_to;
UPDATE tb_account SET balance=balance-money WHERE id=id_from;
END
//

执行效果如下图所示:

在这里插入图片描述
(6) 调用刚刚创建的存储过程proc_transfer,实现从账户A向账户B转账700元,并查看转账结果,代码如下:

CALL proc_transfer(1,2,700);
SELECT * FROM tb_account;

执行效果如下图所示:

在这里插入图片描述
从上图中可以看出,A账户的余额由于原来的1000变为300,减少了700元,而B账户的余额则多了700元,由此可见,转账成功。

(7) 再一次调用存储过程proc_transfer,实现从账户A向账户B转账500元,并查看转账结果,代码如下:

在这里插入图片描述
从上图可以看出,在进行第二次转账时,由于第一个账户的余额不能小于零,所以出现了错误。但是在查询账户余额时却发现,第一个账户的余额没有变化,而第二个账户的余额却变化1200,比之前多了500元。这样A和B账户的余额总和就由转账前的1000元,变为1500元了,凭空多了500元,由此产生了数据不一致的问题。

为了避免这种情况,MySQL 中引入了事务的概念。通过在存储过程中,加入事务将原来独立执行的两条 UPDATE 语句绑定在一起,实现只要其中的一个执行不成功,那么两个语句就都不执行,从而保存数据的一致性。

关闭 MySQL 自动提交:

MySQL 默认采用自动提交 (AUTOCOMMIT) 模式。也就是说,如果不显式地开启一个事务,则每个 SQL 语句都被当作一个事务执行提交操作。例如,在上面编写的存储过程proc_transfer中,包括两个更新语句,由于 MySQL 默认开启了自动提交功能,所以,无论第二条语句执行成功与否,都不影响第一条语句的执行结果。因此,对于像银行转账之类的业务逻辑来说,有必要关闭 MySQL 的自动提交功能。

要想查看 MySQL 的自动提交功能是否关闭,可以使用 MySQL 的 SHOW VARIABLES 命令查询 AUTOCOMMIT 变量的值,如果该变量的值为 1 或者 ON 时表示启用,为 0 或者 OFF 时表示禁用。具体代码如下:

SHOW VARIABLES LIKE 'autocommit';

执行上面的代码将显示如下图所示的运行结果:

在这里插入图片描述
在 MySQL 中,关闭自动提交功能可以分为以下两种情况。

(1) 显式关闭自动提交功能

在当前连接中,可以通过将 AUTOCOMMIT 变量设置为0,来禁用自动提交功能。禁用自动提交功能,并且查看修改后的值的具体代码如下:

SET AUTOCOMMIT=0;
SHOW VARIABLES LIKE 'autocommit';

执行结果如下图所示:

在这里插入图片描述
说明:系统变量 AUTOCOMMIT 是会话变量,只在当前命令行窗口有效。即在命令行窗口A中设置的 AUTOCOMMIT 变量值,不会影响到命令行窗口B中该变量的值。

当 AUTOCOMMIT 变量设置为0时,所有的 SQL 语句都是在一个事务中,直到显式地执行提交 (COMMIT) 或者(ROLLBACK) 时,该事务才结束,同时又会开启另一个新事务。

另外,还有一些命令,在执行前会强制执行 COMMIT 提交当前的活动事务。例如,ALTER TABLE。说明:修改 AUTOCOMMIT 变量值对于采用 MyISAM 存储引擎的表没有影响。即无论自动提交功能是否关闭,更新操作都将立即执行,并且将执行结果提交到数据库中,成为数据库永久的组成部分。

(2) 隐式关闭自动提交功能

当使用 START TRANSACTION; 命令时,可以隐式地关闭自动提交功能。该方法不会修改 AUTOCOMMIT 变量的值。

事务回滚:

事务回滚也叫撤消。当关闭自动提交功能后,数据库开发人员可以根据需要回滚更新操作。下面还是以例1的数据库为例进行操作。

【例2】 实现从借记卡账户A向B转账500元,出错时进行事务回滚。具体步骤如下。

(1) 关闭 MySQL 的自动提交功能,代码如下:

SET AUTOCOMMIT=0;

(2) 调用例1编写的存储过程proc_transfer,实现从借记卡账户A向B转账500元,并查看账户余额,代码如下:

SELECT * FROM tb_account;
CALL proc_transfer(1,2,500);
SELECT * FROM tb_account;

执行结果如下图所示。

在这里插入图片描述
从上图中可以看出, B账户中已经多出来500元,由原来的1200元变为1700元了。这时需要确认一下,数据库中是否已经真的接收到了这个变化。

(3) 再重新打开一个 MySQL 命令行窗口,选择 db_bank 数据库为当前数据库,然后查询数据表 tb_account 中的数据,代码如下:

USE db_bank;
SELECT * FROM tb_account;

执行结果如下图所示:

在这里插入图片描述
从上图中可以看出B的余额仍然是转账前的1200元,并没有加上500元。这是因为关闭了 MySQL 的自动提交功能后,如果不手动提交,那么 UPDATE 操作的结果将仅仅影响内存中的临时记录,并没有真正写入数据库文件。所以当前命令行窗口中执行 SELECT 查询语句时,获得的是临时记录,并不是实际数据表中的数据。此时的结果走向取决于接下来执行的操作,如果执行 ROLLBACK (回滚),那么将放弃所做的修改,如果执行 COMMIT (提交),那么会将修改的结果保存到数据库文件,永久保存。

(4) 由于更新后的数据与想要实现的结果不一致,这里执行 ROLLBACK (回滚) 操作,放弃之前的修改。执行回滚操作,并查看余额的代码如下:

ROLLBACK;
SELECT * FROM tb_account;

执行结果如下图所示:

在这里插入图片描述
从上图中可以看出,步骤 (3) 所作的修改被回滚了,也就是放弃了之前所做的修改。

事务提交:

当关闭自动提交功能后,数据库开发人员可以根据需要提交更新操作,否则更新的结果不能提交到数据库文件中,成为数据库永久的组成部分。关闭自动提交功能后,提交事务可以分为以下两种情况。

(1) 显式提交

关闭自动提交功能后,可以使用 COMMIT 命令显式的提交更新语句。例如,例2中,如果将第 (4) 步中的回滚语句替换为提交语句 COMMIT,将得到如下图所示的结果。

在这里插入图片描述
从上图中可以看出,更新操作已经被提交。此时,再打开一个新的命令行窗口查询余额,可以发现得到的结果与上图所示的查询余额得到的结果是一致的。

(2) 隐式提交

关闭自动提交功能后,如果没有手动提交更新操作或者进行过回滚操作,那么执行如下表所示的命令也将执行提交操作。

在这里插入图片描述
例如,在执行了关闭 MySQL 自动提交功能的命令后,执行 SET AUTOCOMMIT=1 命令,此时除了开启自动提交功能,还会提交之前的所有更新语句。

MySQL 中的事务:

在 MySQL 中,应用 START TRANSACTION 命令来标记一个事务的开始。具体的语法格式如下:

START TRANSACTION;

通常 START TRANSACTION 命令后面跟随的是组成事务的 SQL 语句,并且在所有要执行的操作全部完成后,添加 COMMIT 命令,提交事务。下面通过一个具体的实例演示 MySQL 中事务的应用。

【例3】 这里还是以例1的数据库为例进行操作。创建存储过程,并且在该存储过程中创建事务,实现从借记卡账户A向B转账500元,出错时进行事务回滚。具体步骤如下。

(1) 创建存储过程,名称为prog_tran_account,在该存储过程中创建一个事务,实现从一个账户向另一个账户转账功能,具体代码如下:

DELIMITER //
CREATE PROCEDURE prog_tran_account(IN id_from INT,IN id_to INT,IN money int)
MODIFIES SQL DATA
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK;
START TRANSACTION;
UPDATE tb_account SET balance=balance+money WHERE id=id_to;
UPDATE tb_account SET balance=balance-money WHERE id=id_from;
COMMIT;
END
//

执行结果如下图所示:

在这里插入图片描述
(2) 调用刚刚创建的存储过程prog_tran_account,实现从账户A向账户B转账700元,并查看转账结果,代码如下:

CALL prog_tran_account(1,2,700);
SELECT * FROM tb_account;

执行效果如下图所示:

在这里插入图片描述
从上图中可以看出,各账户的余额并没有改变,而且也没有出现错误,这是因为对出现的错误进行了处理,并且进行了事务回滚。如果在调用存储过程时,将其中的转账金额修改为200元,那么将正常实现转账,代码如下:

CALL prog_tran_account(1,2,200);
SELECT * FROM tb_account;

执行结果如下图所示。

在这里插入图片描述
说明:在 MySQL 中,除了可以使用 START TRANSACTION 命令外,还可以使用 BEGIN 或者 BEGIN WORK 命令开启一个事务。通过上面的实例可以得出如下图所示的事务执行流程图:

在这里插入图片描述
回退点:

在默认的情况下,事务一旦回滚,那么事务中的所有更新操作都将被撤销。有时候,并不是想要全部撤消,而是只需要撤消一部分,这时可以通过设置回退点来实现。回退点又称保存点。使用 SAVEPOINT 命令实现在事务中设置一个回退点,具体语法格式如下:

SAVEPOINT 回退点名;

设置回退点后,可以在需要进行事务回滚时,指定该回退点,具体的语法格式如下:

rollback to savepoint 定义的回退点名;

【例4】 创建一个名称为 prog_savepoint_account 的存储过程,在该存储过程中创建一个事务,实现向 tb_account 表中添加一个账户C,并且向该账户存入1000元。然后从A账户向B账户转账500元。当出现错误时,回滚到提前定义的回退点,否则提交事务。具体步骤如下。

(1) 创建存储过程,名称为 prog_savepoint_account,在该存储过程中创建一个事务,实现从一个账户向另一个账户转账功能,并且定义回退点,具体代码如下:

DELIMITER //
CREATE PROCEDURE prog_savepoint_account()
MODIFIES SQL DATA
BEGIN
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK TO A;
COMMIT;
END;
START TRANSACTION;
START TRANSACTION;
INSERT INTO tb_account (name,balance)VALUES('C',1000);
savepoint A;
UPDATE tb_account SET balance=balance+500 WHERE id=2;
UPDATE tb_account SET balance=balance-500 WHERE id=1;
COMMIT;
END
//

(2) 调用刚刚创建的存储过程 prog_tran_account,实现添加账户C和转账功能,并查看转账结果,代码如下:

CALL prog_savepoint_account();
SELECT * FROM tb_account;

执行效果如下图所示。
在这里插入图片描述
从上图中可以看出,第一条插入语句成功执行,后面两条更新语句,由于最后一条更新语句出现错误,所以事务回滚了。

1.3 事务的4种隔离级别 (Isolation Level) 分别是什么?

当多个线程都开启事务操作数据库中的数据时,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性,所以,对于不同的事务,采用不同的隔离级别会有不同的结果。如果不考虑事务的隔离性,那么会发生下表所示的 3 种问题:

在这里插入图片描述
不可重复读是由于事务并发修改同一条记录导致的,要避免这种情况,最简单的方法就是对要修改的记录加锁,这会导致锁竞争加剧,影响性能。另一种方法是通过 MVCC 可以在无锁的情况下,避免不可重复读。

幻读是由于并发事务增加记录导致的,这个不能像不可重复读通过记录加锁解决,因为对于新增的记录根本无法加锁。需要将事务串行化,才能避免幻读。

脏读和不可重复读的区别:脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是在同一个事务范围内多次查询同一条数据却返回了不同的数据值,这是由于在查询间隔期间,该条数据被另一个事务修改并提交了。

幻读和不可重复读的区别:幻读和不可重复读都是读取了另一个事务中已经提交的数据,不同的是不可重复读多次查询的都是同一个数据项,针对的是对同一行数据的修改或删除(UPDATE、DELETE),而幻读针对的是一个数据整体 (例如,数据的条数),主要是 INSERT 操作。

在 SQL 标准中定义了 4 种隔离级别,每一种级别都规定了一个事务中所做的修改,哪些是在事务内和事务间可见的,哪些是不可见的。较低级别的隔离通常可以执行更高的并发,系统的开销也更低。SQL 标准定义的四个隔离级别为 Read Uncommitted (未提交读)、Read Committed (提交读)、Repeatable Read (可重复读)、Serializable (可串行化),下面分别介绍。

(1) Read Uncommitted (未提交读,读取未提交内容)
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果,即在未提交读级别,事务中的修改,即使没有提交,对其他事务也都是可见的,该隔离级别很少用于实际应用。读取未提交的数据,也被称之为脏读 (Dirty Read)。该隔离级别最低,并发性能最高。

(2) Read Committed (提交读,读取提交内容)
这是大多数数据库系统的默认隔离级别。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。换句话说,一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。

(3) Repeatable Read (可重复读)
可重复读可以确保同一个事务,在多次读取同样的数据的时候,得到同样的结果。可重复读解决了脏读的问题,不过理论上,这会导致另一个棘手的问题:幻读(Phantom Read)。MySQL 数据库中的 InnoDB 和 Falcon 存储引擎通过 MVCC (Multi-Version Concurrent Control,多版本并发控制) 机制解决了该问题。需要注意的是,多版本只是解决不可重复读问题,而加上间隙锁 (也就是它这里所谓的并发控制) 才解
决了幻读问题。
(4) Serializable (可串行化、序列化)
这是最高的隔离级别,它通过强制事务排序,强制事务串行执行,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。实际应用中也很少用到这个隔离级别,只有在非常需要确保数据的一致性而且可以接受没有并发的情况下,才考虑用该级别。这是花费代价最高但是最可靠的事务隔离级别。不同的隔离级别有不同的现象,并有不同的锁和并发机制,隔离级别越高,数据库的并发性能就越差,4 种事务隔离级别与并发性能的关系:

在这里插入图片描述

1.4 Oracle、MySQL、和 SQL Server 中的事务隔离级别

Oracle、MySQL 和 SQL Server 中的事务隔离级别参考下表:

在这里插入图片描述
在这里插入图片描述

1.5 什么是范式?

当设计关系型数据库时,需要遵从不同的规范要求,设计出合理的关系型数据库,这些不同的规范要求被称为不同的范式 (Normal Form),越高的范式数据库冗余越小。应用数据库范式可以带来许多好处,但是最主要的目的是为了消除重复数据,减少数据冗余,让数据库内的数据更好地组织,让磁盘空间得到更有效地利用。范式的缺点:范式使查询变得相当复杂,在查询时需要更多的连接,一些复合索引的列由于范式化的需要被分割到不同的表中,导致索引策略不佳。

1.6 什么是第一、二、三、BC范式?

所谓 第几范式,是表示关系的某一种级别,所以经常称某一关系 R 为第几范式。目前关系型数据库有六种范式:第一范式 (1NF)、第二范式 (2NF)、第三范式 (3NF)、巴斯-科德范式 (BCNF)、第四范式 (4NF) 和 第五范式 (5NF,又称完美范式)。满足最低要求的范式是第一范式 (1NF)。在第一范式的基础上进一步满足更多规范要求的称为第二范式 (2NF),其余范式以此类推。一般说来,数据库只需满足第三范式 (3NF) 就行了。满足高等级的范式的先决条件是必须先满足低等级范式。

在关系型数据库中,关系是通过表来表示的。在一个表中,每一行代表一个联系,而一个关系就是由许多的联系组成的集合。所以,在关系模型中,关系用来指代表,而元组用来指代行,属性就是表中的列。对于每一个属性,都存在一个允许取值的集合,称为该属性的域。下面介绍范式中会用到的一些常用概念:

在这里插入图片描述
在这里插入图片描述
下表列出了各种范式:

在这里插入图片描述
在这里插入图片描述
四种范式之间存在如下关系:
BCNF ⊆ 3NF ⊆ 2NF ⊆ 1NF

学习了范式,为了巩固理解,接下来设计一个论坛的数据库,该数据库中需要存放如下信息:
(1) 用户:用户名,EMAIL,主页,电话,联系地址。
(2) 帖子:发帖标题,发帖内容,回复标题,回复内容。

第一次可以将数据库设计为仅仅存在一张表:
用户名 EMAIL 主页 电话 联系地址 发帖标题 发帖内容 回复标题 回复内容
这个数据库表符合第一范式,但是没有任何一组候选关键字能决定数据库表的整行,唯一的关键字段用户名也不能完全决定整个元组。所以,需要增加 发帖ID回复ID 字段,即将表修改为
用户名 EMAIL 主页 电话 联系地址 发帖ID 发帖标题 发帖内容 回复ID 回复标题 回复内容
这样数据表中的关键字 (用户名、发帖ID、回复ID) 能决定整行:
(用户名,发帖ID,回复ID)→(EMAIL,主页,电话,联系地址,发帖标题,发帖内容,回复标题,回复内容) 但是,这样的设计不符合第二范式,因为存在如下决定关系:
(用户名) ⇒ (EMAIL,主页,电话,联系地址)
(发帖ID) ⇒ (发帖标题,发帖内容)
(回复ID) ⇒ (回复标题,回复内容)
即非关键字段部分函数依赖于候选关键字段,很明显,这个设计会导致大量的数据冗余和操作异常。

因此,需要对这张表进行分解,具体分解为 (加粗的为关键字):
(1) 用户信息:用户名、EMAIL、主页、电话、联系地址。
(2) 帖子信息:发帖ID、标题、内容。
(3) 回复信息:回复ID、标题、内容。
(4) 发帖:用户名、发帖ID。
(5) 回复:发帖ID、回复ID。

这样的设计是满只第1、2、3 范式 和 BCNF 范式要求的,但是这样的设计是不是最好的呢?不一定。

观察可知,第 4 项 发帖 中的 用户名发帖ID 之间是 1:N 的关系,因此,可以把发帖合并到第 2 项的 帖子信息 中,第 5 项 回复 中的 发帖ID回复ID 之间也是 1:N 的关系,因此,可以把回复合并到第 3 项的 回复信息 中。这样可以一定程度地减少数据冗余,新的设计如下所示:

(1) 用户信息:用户名、EMAIL、主页、电话、联系地址。
(2) 帖子信息:用户名、发帖ID、标题、内容。
(3) 回复信息:发帖ID、回复ID、标题、内容。

数据库表1显然满足所有范式的要求。数据库表 2 中存在非关键字段 标题 内容 对关键字段 发帖ID 的部分函数依赖,满足第二范式的要求,但是这一设计并不会导致数据冗余和操作异常。数据库表 3 中也存在非关键字段 标题 内容 对关键字段 回复ID 的部分函数依赖,也不满足第二范式的要求,但是与数据库表 2 相似,这一设计也不会导致数据冗余和操作异常。

由此可以看出,并不一定要强行满足范式的要求,对于 1:N 关系,当 1 的一边合并到 N 的那边后,N 的那边就不再满足第二范式了,但是这种设计反而比较好。对于 M:N 的关系,不能将 M 一边或 N 一边合并到另一边去,这样会导致不符合范式要求,同时导致操作异常和数据冗余。对于 1:1 的关系,可以将左边的 1 或者右边的 1 合并到另一边去,设计导致不符合范式要求,但是并不会导致操作异常和数据冗余。

所以,满足范式要求的数据库设计是结构清晰的,同时可避免数据冗余和操作异常。这并意味着不符合范式要求的设计一定是错误的,在数据库表中存在 1:11:N 关系这种较特殊的情况下,合并导致的不符合范式要求反而是合理的。所以,在数据库设计的时候,一定要时刻考虑范式的要求。

1.7 什么是反范式?

数据库设计要严格遵守范式,这样设计出来的数据库,虽然思路很清晰,结构也很合理,但是有时候却要在一定程度上打破范式设计。因为范式越高,设计出来的表可能越多,关系可能越复杂,但是性能却不一定好,因为表一多,就增加了关联性。特别是在高可用的 OLTP 数据库中,这一点就表现得很明显,所以就引入了反范式。

不满足范式的模型,就是反范式模型。反范式跟范式所要求的正好相反,在反范式的设计模式中,可以允许适当的数据冗余。用这个冗余可以缩短查询获取数据的时间。反范式其本质上就是用空间来换取时间,把数据冗余在多个表中,当查询时就可以减少或者避免表之间的关联。反范式技术也可以称为反规范化技术。

反范式的优点:减少了数据库查询时表之间的连接次数,可以更好地利用索引进行筛选和排序,从而减少了 I/O 数据量,提高了查询效率。

反范式的缺点:数据存在重复和冗余,存在部分空间浪费。另外,为了保持数据的一致性,则必须维护这部分冗余数据,因此增加了维护的复杂性。所以,在进行范式设计时,要在数据一致性与查询之间找到平衡点,因为符合业务场景的设计才是好的设计。在 RDBMS 模型设计过程中,常常使用范式来约束模型,但在 NoSQL 模型中则大量采用反范式。常见的数据库反范式技术包括:

  1. 增加冗余列:在多个表中保留相同的列,以减少表连接的次数。冗余法以空间换取时间,把数据冗余在多个表中,当查询时可以减少或者是避免表之间的关联。
  2. 增加派生列:表中增加可以由本表或其他表中数据计算生成的列,减少查询时的连接操作并避免计算或使用集合函数。
  3. 表水平分割:根据一列或多列的值将数据放到多个独立的表中,主要用于表的规模很大、表中数据相对独立或数据需要存放到多个介质的情况。
  4. 表垂直分割:对表按列进行分割,将主键和一部分列放到一个表中,主键与其他列放到另一个表中,在查询时减少 I/O 次数。

举例,有学生表与课程表,假定课程表要经常被查询,而且在查询中要显示学生的姓名,则查询语句为:

SELECT CODE,NAME,SUBJECT FROM COURSE C,STUDENTS S WHERE S.ID=C.CODE WHERE CODE=?

如果这个语句被大范围、高频率执行,那么可能会因为表关联造成一定程度的影响,现在,假定评估到学生改名的需求是非常少的,那么,就可以把学生姓名冗余到课程表中。注意:这里并没有省略学生表,只不过是把学生姓名冗余在了课程表中,如果万一有很少的改名需求,只要保证在课程表中改名正确即可。那么,修改以后的语句可以简化为:

SELECT CODE,NAME,SUBJECT FROM COURSE C WHERE CODE=?

范式和反范式的对例如下表所示:

在这里插入图片描述

1.8 索引的使用原则有哪些?

(1) 在大表上建立索引才有意义。
(2) 在 WHERE 子句或是连接条件经常引用的列上建立索引。
(3) 索引的层次不要超过4层。
(4) 如果某属性常作为最大值和最小值等聚集函数的参数,那么考虑为该属性建立索引。
(5) 表的主键、外键必须有索引。
(6) 创建了主键和唯一约束后会自动创建唯一索引。
(7) 经常与其他表进行连接的表,在连接字段上应该建立索引。
(8) 经常出现在 WHERE 子句中的字段,特别是大表的字段,应该建立索引。
(9) 要索引的列经常被查询,并只返回表中的行的总数的一小部分。
(10) 对于那些查询中很少涉及的列、重复值比较多的列尽量不要建立索引。
(11) 经常出现在关键字 ORDER BY、GROUP BY、DISTINCT 后面的字段,最好建立索引。
(12) 索引应该建在选择性高的字段上。
(13) 索引应该建在小字段上,对于大的文本字段甚至超长字段,不适合建索引。对于定义为 CLOB、TEXT、IMAGE 和 BIT 的数据类型的列不适合建立索引。
(14) 复合索引的建立需要进行仔细分析。正确选择复合索引中的前导列字段,一般是选择性较好的字段。
(15) 如果单字段查询很少甚至没有,那么可以建立复合索引;否则考虑单字段索引。
(16) 如果复合索引中包含的字段经常单独出现在 WHERE 子句中,那么分解为多个单字段索引。
(17) 如果复合索引所包含的字段超过3个,那么仔细考虑其必要性,考虑减少复合的字段。
(18) 如果既有单字段索引,又有这几个字段上的复合索引,那么一般可以删除复合索引。
(19) 频繁进行 DML 操作的表,不要建立太多的索引。
(20) 删除无用的索引,避免对执行计划造成负面影响。

水可载舟,亦可覆舟,索引也一样。索引有助于提高检索性能,但过多或不当的索引也会导致系统低效。

1.9 什么是存储过程?它有什么优点?

存储过程是用户定义的一系列 SQL 语句的集合,涉及特定表或其他对象的任务,用户可以调用存储过程,而函数通常是数据库已定义的方法,它接收参数并返回某种类型的值并且不涉及特定用户表。

存储过程用于执行特定的操作,可以接受输入参数、输出参数、返回单个或多个结果集。在创建存储过程时,既可以指定输入参数 (IN),也可以指定输出参数 (OUT),通过在存储过程中使用输入参数,可以将数据传递到执行部分;通过使用输出参数,可以将执行结果传递到应用环境。存储过程可以使对数据库的管理、显示数据库及其用户信息的工作更加容易。

存储过程存储在数据库内,可由应用程序调用执行。存储过程允许用户声明变量并且可包含程序流、逻辑以及对数据库的查询。具体而言,存储过程的优点如下所示:

(1) 存储过程增强了 SQL 语言的功能和灵活性。存储过程可以用流控制语句编写,有很强的灵活性,可以完成复杂的判断和运算。
(2) 存储过程可保证数据的安全性。通过存储过程可以使没有权限的用户在权限控制之下间接地存取数据库中的数据,从而保证数据的安全。
(3) 通过存储过程可以使相关的动作在一起发生,从而维护数据库的完整性。
(4) 在运行存储过程前,数据库已对其进行了语法和句法分析,并给出了优化执行方案。这种已经编译好的过程可极大地改善 SQL 语句的性能。由于执行 SQL 语句的大部分工作已经完成,所以,存储过程能以极快的速度执行。
(5) 可以降低网络的通信量,因为不需要通过网络来传送很多 SQL 语句到数据库服务器。
(6) 把体现企业规则的运算程序放入数据库服务器中,以便集中控制。当企业规则发生变化时,在数据库中改变存储过程即可,无须修改任何应用程序。企业规则的特点是要经常变化,如果把体现企业规则的运算程序放入应用程序中,那么当企业规则发生变化时,就需要修改应用程序,工作量非常之大 (修改、发行和按照应用程序)。如果把体现企业规则的运算放入存储过程中,那么当企业规则发生变化时,只要修改存储过程就可以了,应用程序无须任何变化。

1.10 存储过程和函数的区别是什么?

存储过程和函数都是存储在数据库中的程序,可由用户直接或间接调用,它们都可以有输出参数,都是由一系列的 SQL 语句组成。具体而言,存储过程和函数的不同点如下所示:

(1) 标识符不同。函数的标识符为 FUNCTION,存储过程为 PROCEDURE。
(2) 函数必须有返回值,且只能返回一个值,而存储过程可以有多个返回值。
(3) 存储过程无返回值类型,不能将结果直接赋值给变量;函数有返回值类型,在调用函数时,除了用在 SELECT 语句中,在其他情况下必须将函数的返回值赋给一个变量。
(4) 函数可以在 SELECT 语句中直接使用,而存储过程不能,例如:假设已有函数 FUN_GETAVG() 返回 NUMBER 类型绝对值。那么,SQL 语句 SELECT FUN_GETAVG(COL_A) FROM TABLE 是合法的。

存储过程和函数都可以有输出参数,都是由一系列的 SQL 语句组成。

1.11 触发器的作用、优缺点有哪些?

触发器 (TRIGGER)是数据库提供给程序员和 DBA 用来保证数据完整性的一种方法,它是与表事件相关的特殊的存储过程,是用户定义在表上的一类由事件驱动的特殊过程。触发器的执行不是由程序调用,也不是由手工启动,而是由事件来触发的,其中,事件是指用户对表的增 (INSERT)、删 (DELETE),改 (即更新UPDATE) 等操作。触发器经常被用于加强数据的完整性约束和业务规则等。

触发器与存储过程的区别在于:存储过程是由用户或应用程序显式调用的,而触发器是不能被直接调用的,而是由一个事件来触发运行,即触发器是当某个事件发生时自动地隐式运行。具体而言,触发器有如下作用:

(1) 可维护数据库的安全性、一致性和完整性。
(2) 可在写入数据表前,强制检验或转换数据。
(3) 当触发器发生错误时,异常的结果会被撤销。
(4) 部分数据库管理系统可以针对数据定义语言 (DDL) 使用触发器,称为 DDL 触发器,还可以针对视图定义替代触发器(INSTEAD OF)。

触发器的优点:触发器可通过数据库中的相关表实现级联更改。从约束的角度而言,触发器可以定义比 CHECK 更为复杂的约束。与 CHECK 约束不同的是,触发器可以引用其他表中的列。例如,触发器可以使用另一个表中的数据来比较更新的数据,以及执行其他操作,如修改数据或显示用户定义错误信息。触发器也可以评估数据修改前后的表的状态,并根据其差异采取对策。一个表中的多个同类触发器 (INSERT、UPDATE 或 DELETE)允许采取多个不同的对策以响应同一个修改语句。

当然,虽然触发器功能强大,可以轻松可靠地实现许多复杂的功能,但是它也具有一些缺点,滥用会造成数据库及应用程序的维护困难。在数据库操作中,可以通过关系、触发器、存储过程及应用程序等来实现数据操作。同时,规则、约束、缺省值也是保证数据完整性的重要保障。如果对触发器过分地依赖,那么势必会影响数据库的结构,同时增加了维护的复杂性。

对于触发器,需要特别注意以下几点内容:
(1) 触发器在数据库里以独立的对象存储。
(2) 存储过程通过其他程序来启动运行或直接启动运行,而触发器是由一个事件来启动运行。即触发器是当某个事件发生时自动地隐式运行。
(3) 触发器被事件触发。运行触发器称为触发或点火(FIRING),用户不能直接调用触发器。
(4) 触发器不能接收参数。

1.12 什么是视图?视图的作用是什么?

视图是由从数据库的基本表中选取出来的数据组成的逻辑窗口,它不同于基本表,它是一个虚拟表,其内容由查询定义。在数据库中,存放的只是视图的定义而已,而不存放数据,这些数据仍然存放在原来的基本表结构中。只有在使用视图的时候,才会执行视图的定义,从基本表中查询数据。

同真实的表一样,视图包含一系列带有名称的列和行数据。但是,视图并不在数据库中以存储的数据值集形式存在。行和列数据来自由定义视图的查询所引用的表,并且在引用视图时动态生成。对其中所引用的基础表而言,视图的作用类似于筛选。定义视图可以来自当前或其他数据库的一个或多个表,或者其他视图。分布式查询也可用于定义使用多个异类源数据的视图。如果有几台不同的服务器分别存储不同地区的数据,那么当需要将这些服务器上相似结构的数据组合起来的时候,这种方式就非常有用。

通过视图进行查询没有任何限制,用户可以将注意力集中在其关心的数据上,而非全部数据,这样就大大提高了运行效率与用户满意度。如果数据来源于多个基本表结构,或者数据不仅来自于基本表结构,还有一部分数据来源于其他视图,并且搜索条件又比较复杂,需要编写的查询语句就会比较繁琐,此时定义视图就可以使数据的查询语句变得简单可行。定义视图可以将表与表之间的复杂的操作连接和搜索条件对用户不可见,用户只需要简单地对一个视图进行查询即可,所以,视图虽然增加了数据的安全性,但是不能提高查询的效率。

视图看上去非常像数据库的物理表,对它的操作同任何其他的表一样。当通过视图修改数据时,实际上是在改变基表 (即视图定义中涉及的表) 中的数据;相反地,基表数据的改变也会自动反映在由基表产生的视图中。由于逻辑上的原因,有些 Oracle 视图可以修改对应的基表,有些则不能 (仅仅能查询) 。

数据库视图的作用有以下几点:
(1) 隐藏了数据的复杂性,可以作为外模式,提供了一定程度的逻辑独立性。
(2) 有利于控制用户对表中某些列或某些机密数据的访问,提高了数据的安全性。
(3) 能够简化结构,执行复杂查询操作。
(4) 使用户能以多种角度、更灵活地观察和共享同一数据。

1.13 什么是 SQL 注入?

所谓 SQL 注入 (SQL Injection),就是通过把 SQL 命令插入到 WEB 表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的 SQL 命令的目的。例如,在代码中使用下面的 SQL 句:

sql = "select * from images where img_id = '%s';" % "000d80f8e14d1bfeb42e403039934f00' or 1 = 1 or '"

那么只要是 images 表中有数据,这条 SQL 语句就会返回结果。这就达到了 SQL 注入的目的,示例代码如下:

import pymysql

if __name__ == '__main__':
    conn = pymysql.connect(host="localhost",
                           port=3306,
                           user="root",
                           password="mysql",
                           database="images360",
                           charset="utf8")

    cursor = conn.cursor()
    sql = "select * from images where img_id = '%s';" % "000d80f8e14d1bfeb42e403039934f00' or 1 = 1 or '"
    print(sql)
    cursor.execute(sql)
    result = cursor.fetchall()
    for row in result:
        print(row)

    cursor.close()
    conn.close()

程序运行结果如下:

在这里插入图片描述
可以看到,我只查询一张图片的信息,却将 images 表中所有的图片信息都查询了出来,这样是非常不合理的。作为 DBA,永远不要信任用户的输入,相反,必须认定用户输入的数据永远都是不安全的,对用户输入的数据必须都进行过滤处理。为了防止 SQL 注入,需要注意以下几个要点:

(1) 永远不要信任用户的输入。可以通过正则表达式或限制长度的方式对用户的输入进行校验,对引号进行转换等。
(2) 永远不要使用动态拼装SQL,可以使用参数化的 SQL 或者直接使用存储过程进行数据查询、存取。示例代码如下:

import pymysql

if __name__ == '__main__':

    conn = pymysql.connect(host="localhost",
                           port=3306,
                           user="root",
                           password="mysql",
                           database="images360",
                           charset="utf8")

    cursor = conn.cursor()
    sql = "select * from images where img_id = %s;"
    print(sql)

    cursor.execute(sql, ("000d80f8e14d1bfeb42e403039934f00",))
    result = cursor.fetchall()
    for row in result:
        print(row)
    cursor.close()
    conn.close()

程序执行结果如下:

在这里插入图片描述
(3) 永远不要使用管理员权限的数据库连接,建议为每个应用赋予单独的权限。
(4) 不要把机密信息直接存放,建议对密码或敏感信息进行加密或 Hash 处理。
(5) 应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误信息进行包装。
(6) SQL 注入的检测一般采取辅助软件或借助网站平台,软件一般采用 SQL 注入检测工具 JSKY,网站平台就有亿思网站安全平台检测工具:MDCSOFT SCAN 等。采用 MDCSOFT-IPS 可以有效地防御 SQL 注入,XSS (Cross Site Scripting,跨站脚本攻击,为了不和层叠样式表 (Cascading Style Sheets,CSS) 的缩写混淆,故将跨站脚本攻击缩写为 XSS) 攻击等。

1.14 什么是 MVCC?

在介绍 MVCC 概念之前,可以先来设想一下数据库系统里的一个问题:在多用户的系统里,假设有多个用户同时读写数据库里的一行记录,那么怎么保证数据的一致性呢? 一个基本的解决方法是对这一行记录加上一把锁,将不同用户对同一行记录的读写操作完全串行化执行,由于同一时刻只有一个用户在操作,因此一致性不存在问题。但是,它存在明显的性能问题:读会阻塞写,写也会阻塞读,整个数据库系统的并发性能将大打折扣。

MVCC (Multi-Version Concurrent Control,多版本并发控制) 广泛使用于数据库系统。MVCC 的目标是在保证数据一致性的前提下,提供一种高并发的访问性能。在 MVCC 协议中,每个用户在连接数据库时看到的是一个具有一致性状态的镜像,每个事务在提交到数据库之前对其他用户均是不可见的。当事务需要更新数据时,不会直接覆盖以前的数据,而是生成一个新的版本的数据,因此一条数据会有多个版本存储,但是同一时刻只有最新的版本号是有效的。因此,读的时候就可以保证总是以当前时刻的版本的数据可以被读到,不论这条数据后来是否被修改或删除。

可以将 MVCC 看成行级锁的一种妥协,它在许多情况下避免了使用锁,同时可以提供更小的开销。根据实现的不同,它可以允许非阻塞读,在写操作进行时,只锁定需要的记录。MVCC 会保存某个时间点上的数据快照,这意味着事务可以看到一个一致的数据视图,而不管它们需要运行多久。这同时也意味着不同的事务在同一个时间点看到的同一个表的数据可能是不同的。

使用 MVCC 多版本并发控制比锁定模型的主要优点是,在 MVCC 里,对检索 (读) 数据的锁要求与写数据的锁要求不冲突,所以,读不会阻塞写,而写也从不阻塞读。在数据库里也有表和行级别的锁定机制,用于给那些无法轻松接受 MVCC 行为的应用。不过,恰当地使用 MVCC 总会提供比锁更好的性能。

大多数的 MySQL 事务型存储引擎,例如 InnoDB、Falcon 以及 PBXT 都不使用简单的行锁机制,它们都和 MVCC 机制来一起使用。MVCC 不只使用在 MySQL 中,Oracle、PostgreSQL,以及其他一些数据库系统也同样使用它。

1.15 锁的作用有哪些?

锁 (Lock) 机制用于管理对共享资源的并发访问,用于多用户的环境下,可以保证数据库的完整性和一致性。以商场的试衣间为例,每个试衣间都可供多个消费者使用,因此,可能出现多个消费者同时需要使用试衣间试衣服。为了避免冲突,试衣间装了锁,某一个试衣服的人在试衣间里把锁锁住了,其他顾客就不能再从外面打开了,只能等待里面的顾客试完衣服,从里面把锁打开,外面的人才能进去。

当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制,则就有可能会读取和存储到不正确的数据,破坏数据库的完整性和一致性。当事务在对某个数据对象进行操作前,先向系统发出请求,对其加锁。加锁后事务就对该数据对象有了一定的控制。

1.16 更新丢失指的是什么?

更新丢失是指多个用户通过应用程序访问数据库时,由于查询数据并返回到页面和用户修改完毕点击保存按钮将修改后的结果保存到数据库这个时间段 (即修改数据在页面上停留的时间) 在不同用户之间可能存在偏差,从而最先查询数据并且最后提交数据的用户会把其他用户所作的修改覆盖掉。当两个或多个事务选择同一行数据,然后基于最初选定的值更新该行时,会发生丢失更新问题。每个事务都不知道其他事务的存在。最后的更新将重写由其他事务所做的更新,这将导致数据丢失。

简单来说,更新丢失就是两个事务都同时更新一行数据,一个事务对数据的更新把另一个事务对数据的更新覆盖了。这是因为系统没有执行任何的锁操作,因此并发事务并没有被隔离开来。Serializable 可以防止更新丢失问题的发生。其他的三个隔离级别都有可能发生更新丢失问题。Serializable 虽然可以防止更新丢失,但是效率太低,通常数据库不会用这个隔离级别,所以,需要其他的机制来防止更新丢失,例如悲观锁和乐观锁。

更新丢失可以分为以下两类:
第一类丢失更新:在 A 事务撤销时,把已经提交的 B 事务的更新数据覆盖了。这种错误可能造成很严重的问题,通过下面的账户取款转账就可以看出来:

在这里插入图片描述
A 事务在撤销时,不小心将 B 事务已经转入账户的金额给抹去了。

第二类丢失更新:在 A 事务提交时覆盖了 B 事务已经提交的数据,造成 B 事务所做操作丢失:

在这里插入图片描述
上面的例子里由于支票转账事务覆盖了取款事务对存款余额所做的更新,导致银行最后损失了100元,相反,如果转账事务先提交,那么用户账户将损失100元。

1.17 悲观锁和乐观锁

各种大型数据库采用的锁的基本理论是一致的,但在具体实现上各有差别。乐观锁和悲观锁不是数据库中真正存在的锁,只是人们在解决更新丢失时的不同的解决方案,体现的是人们看待事务的态度。下表列出了悲观锁和乐观锁及其更新丢失的解决方案:

在这里插入图片描述

1.18 什么是死锁 (DeadLock) ?

由于资源占用是互斥的,当某几个进程提出申请对方进程占用的资源后,相关进程在无外力协助下,永远分配不到对方进程申请的资源而无法继续运行,这就产生了一种特殊现象------死锁死锁 是当程序中两个或多个进程发生永久阻塞 (等待) 时,而每个进程都在等待被其他进程占用并阻塞了的资源的一种数据库状态。例如,如果进程 A 锁住了记录 1 并等待记录2,而进程 B 锁住了记录 2 并等待记录 1,那么这两个进程就发生了 死锁

在计算机系统中,如果系统的资源分配策略不当,更常见的可能是程序员写的程序有错误等情况下,那么会导致进程因竞争资源不当而产生死锁的现象。

1. 产生原因

(1) 系统资源不足。
(2) 进程运行推进的顺序不合适。
(3) 资源分配不当。
(4) 占用资源的程序崩溃等。

首先,如果系统资源充足,进程的资源请求都能够得到满足,那么死锁出现的可能性就很低,否则,就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能会产生死锁。

2.产生条件

(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:当一个进程因请求资源而被阻塞时,对已获得的资源不会释放。
(3) 不可剥夺条件:进程已获得的资源,在未使用完之前,不能强行被剥夺。
(4) 循环等待条件:若干进程之间形成一种首尾相接的循环等待资源关系。

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

3.解决方法

理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大限度地避免、预防和解除死锁。所以,在系统设计、进程调度等方面注意如何不让产生死锁的条件成立,如何确定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态的情况下占用资源。在系统运行过程中,对进程发出的每一个资源进行动态检查,并根据检查结果决定是否分配资源,若分配后系统可能发生死锁,则不予分配,否则予以分配。因此,对资源的分配要给予合理的规划。

猜你喜欢

转载自blog.csdn.net/xw1680/article/details/109800993