SQL语句执行过程详解

SQL语句执行过程详解,第1张

SQL语句执行过程详解

一条sql,plsql的执行到底是怎样执行的呢?

一、SQL语句执行原理:

第一步:客户端把语句发给服务器端执行当我们在客户端执行 select 语句时,客户端会把这条 SQL 语句发送给服务器端,让服务器端的

进程来处理这语句。也就是说,Oracle 客户端是不会做任何的 *** 作,他的主要任务就是把客户端产生

的一些 SQL 语句发送给服务器端。虽然在客户端也有一个数据进程,但是,这个进程的作用跟服务器

上的进程作用事不相同的。服务器上的数据库进程才会对SQL 语句进行相关的处理。不过,有个问题需

要说明,就是客户端的进程跟服务器的进程是一一对应的。也就是说,在客户端连接上服务器后,在客户

端与服务器端都会形成一个进程,客户端上的我们叫做客户端进程;而服务器上的我们叫做服务器进程。

第二步:语句解析

当客户端把 SQL 语句传送到服务器后,服务器进程会对该语句进行解析。同理,这个解析的工作,

也是在服务器端所进行的。虽然这只是一个解析的动作,但是,其会做很多“小动作”。

1 查询高速缓存(library cache)。服务器进程在接到客户端传送过来的 SQL 语句时,不

会直接去数据库查询。而是会先在数据库的高速缓存中去查找,是否存在相同语句的执行计划。如果在

数据高速缓存中,则服务器进程就会直接执行这个 SQL 语句,省去后续的工作。所以,采用高速数据缓

存的话,可以提高 SQL 语句的查询效率。一方面是从内存中读取数据要比从硬盘中的数据文件中读取

数据效率要高,另一方面,也是因为这个语句解析的原因。

不过这里要注意一点,这个数据缓存跟有些客户端软件的数据缓存是两码事。有些客户端软件为了

提高查询效率,会在应用软件的客户端设置数据缓存。由于这些数据缓存的存在,可以提高客户端应用软

件的查询效率。但是,若其他人在服务器进行了相关的修改,由于应用软件数据缓存的存在,导致修改的

数据不能及时反映到客户端上。从这也可以看出,应用软件的数据缓存跟数据库服务器的高速数据缓存

不是一码事。

2 语句合法性检查(data dict cache)。当在高速缓存中找不到对应的 SQL 语句时,则服

务器进程就会开始检查这条语句的合法性。这里主要是对 SQL 语句的语法进行检查,看看其是否合乎

语法规则。如果服务器进程认为这条 SQL 语句不符合语法规则的时候,就会把这个错误信息,反馈给客

户端。在这个语法检查的过程中,不会对 SQL 语句中所包含的表名、列名等等进行 SQL 他只是语法

上的检查。

3 语言含义检查(data dict cache)。若 SQL 语句符合语法上的定义的话,则服务器进程

接下去会对语句中的字段、表等内容进行检查。看看这些字段、表是否在数据库中。如果表名与列名不

准确的话,则数据库会就会反馈错误信息给客户端。所以,有时候我们写 select 语句的时候,若语法

与表名或者列名同时写错的话,则系统是先提示说语法错误,等到语法完全正确后,再提示说列名或表名

错误。

4 获得对象解析锁(control structer)。当语法、语义都正确后,系统就会对我们需要查询

的对象加锁。这主要是为了保障数据的一致性,防止我们在查询的过程中,其他用户对这个对象的结构发

生改变。

5 数据访问权限的核对(data dict cache)。当语法、语义通过检查之后,客户端还不一定

能够取得数据。服务器进程还会检查,你所连接的用户是否有这个数据访问的权限。若你连接上服务器

的用户不具有数据访问权限的话,则客户端就不能够取得这些数据。有时候我们查询数据的时候,辛辛苦

苦地把 SQL 语句写好、编译通过,但是,最后系统返回个 “没有权限访问数据”的错误信息,让我们气

半死。这在前端应用软件开发调试的过程中,可能会碰到。所以,要注意这个问题,数据库服务器进程先

检查语法与语义,然后才会检查访问权限。

6 确定最佳执行计划 。当语句与语法都没有问题,权限也匹配的话,服务器进程还是不会直接对

数据库文件进行查询。服务器进程会根据一定的规则,对这条语句进行优化。不过要注意,这个优化是有

限的。一般在应用软件开发的过程中,需要对数据库的 sql 语言进行优化,这个优化的作用要大大地大

于服务器进程的自我优化。所以,一般在应用软件开发的时候,数据库的优化是少不了的。当服务器进程

的优化器确定这条查询语句的最佳执行计划后,就会将这条 SQL 语句与执行计划保存到数据高速缓存

(library cache)。如此的话,等以后还有这个查询时,就会省略以上的语法、语义与权限检查的步骤,

而直接执行 SQL 语句,提高 SQL 语句处理效率。

第三步:语句执行

语句解析只是对 SQL 语句的语法进行解析,以确保服务器能够知道这条语句到底表达的是什么意

思。等到语句解析完成之后,数据库服务器进程才会真正的执行这条 SQL 语句。这个语句执行也分两

种情况。

一是若被选择行所在的数据块已经被读取到数据缓冲区的话,则服务器进程会直接把这个数据传递

给客户端,而不是从数据库文件中去查询数据。

若数据不在缓冲区中,则服务器进程将从数据库文件中查询相关数据,并把这些数据放入到数据缓冲

区中(buffer cache)。

第四步:提取数据

当语句执行完成之后,查询到的数据还是在服务器进程中,还没有被传送到客户端的用户进程。所以,

在服务器端的进程中,有一个专门负责数据提取的一段代码。他的作用就是把查询到的数据结果返回给

用户端进程,从而完成整个查询动作。从这整个查询处理过程中,我们在数据库开发或者应用软件开发过

程中,需要注意以下几点:

一是要了解数据库缓存跟应用软件缓存是两码事情。数据库缓存只有在数据库服务器端才存在,在

客户端是不存在的。只有如此,才能够保证数据库缓存中的内容跟数据库文件的内容一致。才能够根据

相关的规则,防止数据脏读、错读的发生。而应用软件所涉及的数据缓存,由于跟数据库缓存不是一码事

情,所以,应用软件的数据缓存虽然可以提高数据的查询效率,但是,却打破了数据一致性的要求,有时候

会发生脏读、错读等情况的发生。所以,有时候,在应用软件上有专门一个功能,用来在必要的时候清除

数据缓存。不过,这个数据缓存的清除,也只是清除本机上的数据缓存,或者说,只是清除这个应用程序

的数据缓存,而不会清除数据库的数据缓存。

二是绝大部分 SQL 语句都是按照这个处理过程处理的。我们 DBA 或者基于 Oracle 数据库的

开发人员了解这些语句的处理过程,对于我们进行涉及到 SQL 语句的开发与调试,是非常有帮助的。有

时候,掌握这些处理原则,可以减少我们排错的时间。特别要注意,数据库是把数据查询权限的审查放在

语法语义的后面进行检查的。所以,有时会若光用数据库的权限控制原则,可能还不能满足应用软件权限

控制的需要。此时,就需要应用软件的前台设置,实现权限管理的要求。而且,有时应用数据库的权限管

理,也有点显得繁琐,会增加服务器处理的工作量。因此,对于记录、字段等的查询权限控制,大部分程

序涉及人员喜欢在应用程序中实现,而不是在数据库上实现。

DBCC DROPCLEANBUFFERS

从缓冲池中删除所有清除缓冲区。

DBCC FREEPROCCACHE

从过程缓存中删除所有元素。

DBCC FREESYSTEMCACHE

从所有缓存中释放所有未使用的缓存条目

SQL语句中的函数、关键字、排序等执行顺序:

1 FROM 子句返回初始结果集。

2 WHERE 子句排除不满足搜索条件的行。

3 GROUP BY 子句将选定的行收集到 GROUP BY 子句中各个唯一值的组中。

4 选择列表中指定的聚合函数可以计算各组的汇总值。

5 此外,HAVING 子句排除不满足搜索条件的行。

6 计算所有的表达式;

7 使用 order by 对结果集进行排序。

8 查找你要搜索的字段。

二、SQL语句执行完整过程:

1用户进程提交一个 sql 语句:

update temp set a=a2,给服务器进程。

2服务器进程从用户进程把信息接收到后,在 PGA 中就要此进程分配所需内存,存储相关的信息,如在会

话内存存储相关的登录信息等。

3服务器进程把这个 sql 语句的字符转化为 ASCII 等效数字码,接着这个 ASCII 码被传递给一个

HASH 函数,并返回一个 hash 值,然后服务器进程将到shared pool 中的 library cache 中去查找是否存在相

同的 hash 值,如果存在,服务器进程将使用这条语句已高速缓存在 SHARED POOL 的library cache 中的已

分析过的版本来执行。

4如果不存在,服务器进程将在 CGA 中,配合 UGA 内容对 sql,进行语法分析,首先检查语法的正确性,接

着对语句中涉及的表,索引,视图等对象进行解析,并对照数据字典检查这些对象的名称以及相关结构,并根据

ORACLE 选用的优化模式以及数据字典中是否存在相应对象的统计数据和是否使用了存储大纲来生成一个

执行计划或从存储大纲中选用一个执行计划,然后再用数据字典核对此用户对相应对象的执行权限,最后生成

一个编译代码。

5ORACLE 将这条 sql 语句的本身实际文本、HASH 值、编译代码、与此语名相关联的任何统计数据

和该语句的执行计划缓存在 SHARED POOL 的 library cache中。服务器进程通过 SHARED POOL 锁存

器(shared pool latch)来申请可以向哪些共享 PL/SQL 区中缓存这此内容,也就是说被SHARED POOL 锁存

器锁定的 PL/SQL 区中的块不可被覆盖,因为这些块可能被其它进程所使用。

6在 SQL 分析阶段将用到 LIBRARY

CACHE,从数据字典中核对表、视图等结构的时候,需要将数据

字典从磁盘读入 LIBRARY

CACHE,因此,在读入之前也要使用LIBRARY

CACHE 锁存器(library cache

pin,library cache lock)来申请用于缓存数据字典。 到现在为止,这个 sql 语句已经被编译成可执行的代码了,

但还不知道要 *** 作哪些数据,所以服务器进程还要为这个 sql 准备预处理数据。

7首先服务器进程要判断所需数据是否在 db buffer 存在,如果存在且可用,则直接获取该数据,同时根据

LRU 算法增加其访问计数;如果 buffer 不存在所需数据,则要从数据文件上读取首先服务器进程将在表头部

请求 TM 锁(保证此事务执行过程其他用户不能修改表的结构),如果成功加 TM 锁,再请求一些行级锁(TX

锁),如果 TM、TX 锁都成功加锁,那么才开始从数据文件读数据,在读数据之前,要先为读取的文件准备好

buffer 空间。服务器进程需要扫面 LRU list 寻找 free db buffer,扫描的过程中,服务器进程会把发现的所有

已经被修改过的 db buffer 注册到 dirty list 中, 这些 dirty buffer 会通过 dbwr 的触发条件,随后会被写出到

数据文件,找到了足够的空闲 buffer,就可以把请求的数据行所在的数据块放入到 db buffer 的空闲区域或者

覆盖已经被挤出 LRU list 的非脏数据块缓冲区,并排列在 LRU list 的头部,也就是在数据块放入 DB

BUFFER 之前也是要先申请 db buffer 中的锁存器,成功加锁后,才能读数据到 db buffer。

8记日志 现在数据已经被读入到 db buffer 了,现在服务器进程将该语句所影响的并被读

入 db buffer 中的这些行数据的 rowid 及要更新的原值和新值及 scn 等信息从 PGA 逐条的写入 redo log

buffer 中。在写入 redo log buffer 之前也要事先请求 redo log buffer 的锁存器,成功加锁后才开始写入,当

写入达到 redo log buffer 大小的三分之一或写入量达到 1M 或超过三秒后或发生检查点时或者 dbwr 之前

发生,都会触发 lgwr 进程把 redo log buffer 的数据写入磁盘上的 redo file 文件中(这个时候会产生log file

sync 等待事件)

已经被写入 redofile 的 redo log buffer 所持有的锁存器会被释放,并可被后来的写入信息覆盖,

redo log buffer是循环使用的。Redo file 也是循环使用的,当一个 redo file 写满后,lgwr 进程会自动切换到

下一 redo file(这个时候可能出现 log fileswitch(checkpoint complete)等待事件)。如果是归档模式,归档进

程还要将前一个写满的 redo file 文件的内容写到归档日志文件中(这个时候可能出现 log file

switch(archiving needed)。

9为事务建立回滚段 在完成本事务所有相关的 redo log buffer 之后,服务器进程开始改写这个 db buffer

的块头部事务列表并写入 scn,然后 copy 包含这个块的头部事务列表及 scn 信息的数据副本放入回滚段中,将

这时回滚段中的信息称为数据块的“前映像“,这个”前映像“用于以后的回滚、恢复和一致性读。(回滚段可以

存储在专门的回滚表空间中,这个表空间由一个或多个物理文件组成,并专用于回滚表空间,回滚段也可在其它

表空间中的数据文件中开辟。

10本事务修改数据块 准备工作都已经做好了,现在可以改写 db buffer 块的数据内容了,并在块的头部写

入回滚段的地址。

11放入 dirty list 如果一个行数据多次 update 而未 commit,则在回滚段中将会有多个“前映像“,除了第

一个”前映像“含有 scn 信息外,其他每个“前映像“的头部都有 scn 信息和“前前映像”回滚段地址。一个

update 只对应一个 scn,然后服务器进程将在 dirty list 中建立一

条指向此 db buffer 块的指针(方便 dbwr 进程可以找到 dirty list 的 db buffer 数据块并写入数据文件中)。

接着服务器进程会从数据文件中继续读入第二个数据块,重复前一数据块的动作,数据块的读入、记日志、建

立回滚段、修改数据块、放入 dirty list。当 dirty queue 的长度达到阀值(一般是 25%),服务器进程将通知

dbwr 把脏数据写出,就是释放 db buffer 上的锁存器,腾出更多的 free db buffer。前面一直都是在说明

oracle 一次读一个数据块,其实 oracle 可以一次读入多个数据块(db_file_multiblock_read_count 来设置一

次读入块的个数)

说明:

在预处理的数据已经缓存在 db buffer 或刚刚被从数据文件读入到 db buffer 中,就要根据 sql 语句

的类型来决定接下来如何 *** 作。

1>如果是 select 语句,则要查看 db buffer 块的头部是否有事务,如果有事务,则从回滚段中读取数据;如

果没有事务,则比较 select 的 scn 和 db buffer 块头部的 scn,如果前者小于后者,仍然要从回滚段中读取数据;

如果前者大于后者,说明这是一非脏缓存,可以直接读取这个 db buffer 块的中内容。

2>如果是 DML *** 作,则即使在 db buffer 中找到一个没有事务,而且 SCN 比自己小的非脏

缓存数据块,服务器进程仍然要到表的头部对这条记录申请加锁,加锁成功才能进行后续动作,如果不成功,则要

等待前面的进程解锁后才能进行动作(这个时候阻塞是 tx 锁阻塞)。

用户 commit 或 rollback 到现在为止,数据已经在 db buffer 或数据文件中修改完

成,但是否要永久写到数文件中,要由用户来决定 commit(保存更改到数据文件) rollback 撤销数据的更改)。

1用户执行 commit 命令

只有当 sql 语句所影响的所有行所在的最后一个块被读入 db buffer 并且重做信息被写入 redo log

buffer(仅指日志缓冲区,而不包括日志文件)之后,用户才可以发去 commit 命令,commit 触发 lgwr 进程,但不

强制立即 dbwr来释放所有相应 db buffer 块的锁(也就是no-force-at-commit,即提交不强制写),也就是说有

可能虽然已经 commit 了,但在随后的一段时间内 dbwr 还在写这条 sql 语句所涉及的数据块。表头部的行锁

并不在 commit 之后立即释放,而是要等 dbwr 进程完成之后才释放,这就可能会出现一个用户请求另一用户

已经 commit 的资源不成功的现象。

A 从 Commit 和 dbwr 进程结束之间的时间很短,如果恰巧在 commit 之后,dbwr 未结束之前断电,因为

commit 之后的数据已经属于数据文件的内容,但这部分文件没有完全写入到数据文件中。所以需要前滚。由

于 commit 已经触发 lgwr,这些所有未来得及写入数据文件的更改会在实例重启后,由 smon 进程根据重做日

志文件来前滚,完成之前 commit 未完成的工作(即把更改写入数据文件)。

B如果未 commit 就断电了,因为数据已经在 db buffer 更改了,没有 commit,说明这部分数据不属于数

据文件,由于 dbwr 之前触发 lgwr 也就是只要数据更改,(肯定要先有 log) 所有 DBWR,在数据文件上的修改

都会被先一步记入重做日志文件,实例重启后,SMON 进程再根据重做日志文件来回滚。

其实 smon 的前滚回滚是根据检查点来完成的,当一个全部检查点发生的时候,首先让 LGWR 进程将

redo log buffer 中的所有缓冲(包含未提交的重做信息)写入重做日志文件,然后让 dbwr 进程将 db buffer 已

提交的缓冲写入数据文件(不强制写未提交的)。然后更新控制文件和数据文件头部的 SCN,表明当前数据库

是一致的,在相邻的两个检查点之间有很多事务,有提交和未提交的。

像前面的前滚回滚比较完整的说法是如下的说明:

A发生检查点之前断电,并且当时有一个未提交的改变正在进行,实例重启之后,SMON 进程将从上一个

检查点开始核对这个检查点之后记录在重做日志文件中已提交的和未提交改变,因为

dbwr 之前会触发 lgwr,所以 dbwr 对数据文件的修改一定会被先记录在重做日志文件中。因此,断电前被

DBWN 写进数据文件的改变将通过重做日志文件中的记录进行还原,叫做回滚,

B 如果断电时有一个已提交,但 dbwr 动作还没有完全完成的改变存在,因为已经提交,提交会触发 lgwr

进程,所以不管 dbwr 动作是否已完成,该语句将要影响的行及其产生的结果一定已经记录在重做日志文件中

了,则实例重启后,SMON 进程根据重做日志文件进行前滚

实例失败后用于恢复的时间由两个检查点之间的间隔大小来决定,可以通个四个参数设置检查点执行的频

率:

Log_checkpoint_interval:

决定两个检查点之间写入重做日志文件的系统物理块(redo blocks)

的大小,默认值是 0,无限制。

log_checkpoint_timeout:

两 个 检 查 点 之 间 的 时 间 长 度(秒)默 认 值 1800s。

fast_start_io_target:

决定了用于恢复时需要处理的块的多少,默认值是 0,无限制。

fast_start_mttr_target:

直接决定了用于恢复的时间的长短,默认值是 0,无限制(SMON 进程执行的前滚

和回滚与用户的回滚是不同的,SMON 是根据重做日志文件进行前滚或回滚,而用户的回滚一定是根据回滚段

的内容进行回滚的。

在这里要说一下回滚段存储的数据,假如是 delete *** 作,则回滚段将会记录整个行的数据,假如是 update,

则回滚段只记录被修改了的字段的变化前的数据(前映像),也就是没有被修改的字段是不会被记录的,假如是

insert,则回滚段只记录插入记录的 rowid。 这样假如事务提交,那回滚段中简单标记该事务已经提交;假如是

回退,则如果 *** 作是 delete,回退的时候把回滚段中数据重新写回数据块, *** 作如果是 update,则把变化前数据

修改回去, *** 作如果是 insert,则根据记录的 rowid 把该记录删除。

2如果用户 rollback。

则服务器进程会根据数据文件块和 DB BUFFER 中块的头部的事务列表和 SCN 以及回滚段地址找到

回滚段中相应的修改前的副本,并且用这些原值来还原当前数据文件中已修改但未提交的改变。如果有多个

“前映像”,服务器进程会在一个“前映像”的头部找到“前前映像”的回滚段地址,一直找到同一事务下的最早的

一个“前映像”为止。一旦发出了 COMMIT,用户就不能rollback,这使得 COMMIT 后 DBWR 进程还没有

全部完成的后续动作得到了保障。到现在为例一个事务已经结束了。

说明:

TM 锁:

符合 lock 机制的,用于保护对象的定义不被修改。 TX 锁:

这个锁代表一个事务,是行

级锁,用数据块头、数据记录头的一些字段表示,也是符合 lock 机制,有 resource structure、lock

structure、enqueue 算法。

mysql行锁和表锁

锁是计算机协调多个进程或纯线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所在有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。

 概述

相对其他数据库而言,MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制。

MySQL大致可归纳为以下3种锁:

表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。

行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。

页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般

 MySQL表级锁的锁模式(MyISAM)

MySQL表级锁有两种模式:表共享锁(Table Read Lock)和表独占写锁(Table Write Lock)。

对MyISAM的读 *** 作,不会阻塞其他用户对同一表请求,但会阻塞对同一表的写请求;

对MyISAM的写 *** 作,则会阻塞其他用户对同一表的读和写 *** 作;

MyISAM表的读 *** 作和写 *** 作之间,以及写 *** 作之间是串行的。

当一个线程获得对一个表的写锁后,只有持有锁线程可以对表进行更新 *** 作。其他线程的读、写 *** 作都会等待,直到锁被释放为止。

MySQL表级锁的锁模式

MySQL的表锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。锁模式的兼容如下表

MySQL中的表锁兼容性

当前锁模式/是否兼容/请求锁模式

读锁    是    是    否  

写锁    是    否    否  

可见,对MyISAM表的读 *** 作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;对MyISAM表的写 *** 作,则会阻塞其他用户对同一表的读和写请求;MyISAM表的读和写 *** 作之间,以及写和写 *** 作之间是串行的!(当一线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新 *** 作。其他线程的读、写 *** 作都会等待,直到锁被释放为止。)

如何加表锁

MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新 *** 作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此用户一般不需要直接用LOCK TABLE命令给MyISAM表显式加锁。在本书的示例中,显式加锁基本上都是为了方便而已,并非必须如此。

给MyISAM表显示加锁,一般是为了一定程度模拟事务 *** 作,实现对某一时间点多个表的一致性读取。

要特别说明以下两点内容。

上面的例子在LOCK TABLES时加了‘local’选项,其作用就是在满足MyISAM表并发插入条件的情况下,允许其他用户在表尾插入记录

在用LOCKTABLES给表显式加表锁是时,必须同时取得所有涉及表的锁,并且MySQL支持锁升级。也就是说,在执行LOCK TABLES后,只能访问显式加锁的这些表,不能访问未加锁的表;同时,如果加的是读锁,那么只能执行查询 *** 作,而不能执行更新 *** 作。其实,在自动加锁的情况下也基本如此,MySQL问题一次获得SQL语句所需要的全部锁。这也正是MyISAM表不会出现死锁(Deadlock Free)的原因

一个session使用LOCK TABLE 命令给表film_text加了读锁,这个session可以查询锁定表中的记录,但更新或访问其他表都会提示错误;同时,另外一个session可以查询表中的记录,但更新就会出现锁等待。

当使用LOCK TABLE时,不仅需要一次锁定用到的所有表,而且,同一个表在SQL语句中出现多少次,就要通过与SQL语句中相同的别名锁多少次,否则也会出错!

并发锁

在一定条件下,MyISAM也支持查询和 *** 作的并发进行。

MyISAM存储引擎有一个系统变量concurrent_insert,专门用以控制其并发插入的行为,其值分别可以为0、1或2。

当concurrent_insert设置为0时,不允许并发插入。

当concurrent_insert设置为1时,如果MyISAM允许在一个读表的同时,另一个进程从表尾插入记录。这也是MySQL的默认设置。

当concurrent_insert设置为2时,无论MyISAM表中有没有空洞,都允许在表尾插入记录,都允许在表尾并发插入记录。

可以利用MyISAM存储引擎的并发插入特性,来解决应用中对同一表查询和插入锁争用。例如,将concurrent_insert系统变量为2,总是允许并发插入;同时,通过定期在系统空闲时段执行OPTIONMIZE TABLE语句来整理空间碎片,收到因删除记录而产生的中间空洞。

MyISAM的锁调度

前面讲过,MyISAM存储引擎的读和写锁是互斥,读 *** 作是串行的。那么,一个进程请求某个MyISAM表的读锁,同时另一个进程也请求同一表的写锁,MySQL如何处理呢?答案是写进程先获得锁。不仅如此,即使读进程先请求先到锁等待队列,写请求后到,写锁也会插到读请求之前!这是因为MySQL认为写请求一般比读请求重要。这也正是MyISAM表不太适合于有大量更新 *** 作和查询 *** 作应用的原因,因为,大量的更新 *** 作会造成查询 *** 作很难获得读锁,从而可能永远阻塞。这种情况有时可能会变得非常糟糕!幸好我们可以通过一些设置来调节MyISAM的调度行为。

通过指定启动参数low-priority-updates,使MyISAM引擎默认给予读请求以优先的权利。

通过执行命令SET LOW_PRIORITY_UPDATES=1,使该连接发出的更新请求优先级降低。

通过指定INSERT、UPDATE、DELETE语句的LOW_PRIORITY属性,降低该语句的优先级。

虽然上面3种方法都是要么更新优先,要么查询优先的方法,但还是可以用其来解决查询相对重要的应用(如用户登录系统)中,读锁等待严重的问题。

另外,MySQL也提供了一种折中的办法来调节读写冲突,即给系统参数max_write_lock_count设置一个合适的值,当一个表的读锁达到这个值后,MySQL变暂时将写请求的优先级降低,给读进程一定获得锁的机会。

上面已经讨论了写优先调度机制和解决办法。这里还要强调一点:一些需要长时间运行的查询 *** 作,也会使写进程“饿死”!因此,应用中应尽量避免出现长时间运行的查询 *** 作,不要总想用一条SELECT语句来解决问题。因为这种看似巧妙的SQL语句,往往比较复杂,执行时间较长,在可能的情况下可以通过使用中间表等措施对SQL语句做一定的“分解”,使每一步查询都能在较短时间完成,从而减少锁冲突。如果复杂查询不可避免,应尽量安排在数据库空闲时段执行,比如一些定期统计可以安排在夜间执行。

InnoDB锁问题

InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION);二是采用了行级锁。

行级锁和表级锁本来就有许多不同之处,另外,事务的引入也带来了一些新问题。

1事务(Transaction)及其ACID属性

事务是由一组SQL语句组成的逻辑处理单元,事务具有4属性,通常称为事务的ACID属性。

原性性(Actomicity):事务是一个原子 *** 作单元,其对数据的修改,要么全都执行,要么全都不执行。

一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以 *** 持完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。

隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发 *** 作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。

持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。

2并发事务带来的问题

相对于串行处理来说,并发事务处理能大大增加数据库资源的利用率,提高数据库系统的事务吞吐量,从而可以支持可以支持更多的用户。但并发事务处理也会带来一些问题,主要包括以下几种情况。

更新丢失(Lost Update):当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题——最后的更新覆盖了其他事务所做的更新。例如,两个编辑人员制作了同一文档的电子副本。每个编辑人员独立地更改其副本,然后保存更改后的副本,这样就覆盖了原始文档。最后保存其更改保存其更改副本的编辑人员覆盖另一个编辑人员所做的修改。如果在一个编辑人员完成并提交事务之前,另一个编辑人员不能访问同一文件,则可避免此问题

脏读(Dirty Reads):一个事务正在对一条记录做修改,在这个事务并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”的数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做“脏读”。

不可重复读(Non-Repeatable Reads):一个事务在读取某些数据已经发生了改变、或某些记录已经被删除了!这种现象叫做“不可重复读”。

幻读(Phantom Reads):一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。

3事务隔离级别

在并发事务处理带来的问题中,“更新丢失”通常应该是完全避免的。但防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据加必要的锁来解决,因此,防止更新丢失应该是应用的责任。

“脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。数据库实现事务隔离的方式,基本可以分为以下两种。

一种是在读取数据前,对其加锁,阻止其他事务对数据进行修改。

另一种是不用加任何锁,通过一定机制生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取。从用户的角度,好像是数据库可以提供同一数据的多个版本,因此,这种技术叫做数据多版本并发控制(MultiVersion Concurrency Control,简称MVCC或MCC),也经常称为多版本数据库。

数据库的事务隔离级别越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的,同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关心数据并发访问的能力。

为了解决“隔离”与“并发”的矛盾,ISO/ANSI SQL92定义了4个事务隔离级别,每个级别的隔离程度不同,允许出现的副作用也不同,应用可以根据自己业务逻辑要求,通过选择不同的隔离级别来平衡"隔离"与"并发"的矛盾

事务4种隔离级别比较

隔离级别/读数据一致性及允许的并发副作用    读数据一致性    脏读    不可重复读    幻读  

未提交读(Read uncommitted)

最低级别,只能保证不读取物理上损坏的数据    是    是    是  

已提交度(Read committed)    语句级    否    是    是  

可重复读(Repeatable read)    事务级    否    否    是  

可序列化(Serializable)    最高级别,事务级    否    否    否  

最后要说明的是:各具体数据库并不一定完全实现了上述4个隔离级别,例如,Oracle只提供Read committed和Serializable两个标准级别,另外还自己定义的Read only隔离级别:SQL Server除支持上述ISO/ANSI SQL92定义的4个级别外,还支持一个叫做"快照"的隔离级别,但严格来说它是一个用MVCC实现的Serializable隔离级别。MySQL支持全部4个隔离级别,但在具体实现时,有一些特点,比如在一些隔离级下是采用MVCC一致性读,但某些情况又不是。

获取InonoD行锁争用情况

可以通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况:

如果发现争用比较严重,如Innodb_row_lock_waits和Innodb_row_lock_time_avg的值比较高,还可以通过设置InnoDB Monitors来进一步观察发生锁冲突的表、数据行等,并分析锁争用的原因。 

InnoDB的行锁模式及加锁方法

InnoDB实现了以下两种类型的行锁。

共享锁(s):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。

排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务取得相同的数据集共享读锁和排他写锁。

另外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。

意向共享锁(IS):事务打算给数据行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。

意向排他锁(IX):事务打算给数据行加排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

InnoDB行锁模式兼容性列表

如果一个事务请求的锁模式与当前的锁兼容,InnoDB就请求的锁授予该事务;反之,如果两者两者不兼容,该事务就要等待锁释放。

意向锁是InnoDB自动加的,不需用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及及数据集加排他锁(X);对于普通SELECT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会任何锁;事务可以通过以下语句显示给记录集加共享锁或排锁。

共享锁(S):SELECT FROM table_name WHERE LOCK IN SHARE MODE

排他锁(X):SELECT FROM table_name WHERE  FOR UPDATE

用SELECT IN SHARE MODE获得共享锁,主要用在需要数据依存关系时确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或者DELETE *** 作。但是如果当前事务也需要对该记录进行更新 *** 作,则很有可能造成死锁,对于锁定行记录后需要进行更新 *** 作的应用,应该使用SELECT FOR UPDATE方式获取排他锁。

InnoDB行锁实现方式

InnoDB行锁是通过索引上的索引项来实现的,这一点MySQL与Oracle不同,后者是通过在数据中对相应数据行加锁来实现的。InnoDB这种行锁实现特点意味者:只有通过索引条件检索数据,InnoDB才会使用行级锁,否则,InnoDB将使用表锁!

在实际应用中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。

什么时候使用表锁

对于InnoDB表,在绝大部分情况下都应该使用行级锁,因为事务和行锁往往是我们之所以选择InnoDB表的理由。但在个另特殊事务中,也可以考虑使用表级锁。

第一种情况是:事务需要更新大部分或全部数据,表又比较大,如果使用默认的行锁,不仅这个事务执行效率低,而且可能造成其他事务长时间锁等待和锁冲突,这种情况下可以考虑使用表锁来提高该事务的执行速度。

第二种情况是:事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚。这种情况也可以考虑一次性锁定事务涉及的表,从而避免死锁、减少数据库因事务回滚带来的开销。

当然,应用中这两种事务不能太多,否则,就应该考虑使用MyISAM表。

在InnoDB下 ,使用表锁要注意以下两点。

(1)使用LOCK TALBES虽然可以给InnoDB加表级锁,但必须说明的是,表锁不是由InnoDB存储引擎层管理的,而是由其上一层MySQL Server负责的,仅当autocommit=0、innodb_table_lock=1(默认设置)时,InnoDB层才能知道MySQL加的表锁,MySQL Server才能感知InnoDB加的行锁,这种情况下,InnoDB才能自动识别涉及表级锁的死锁;否则,InnoDB将无法自动检测并处理这种死锁。

(2)在用LOCAK TABLES对InnoDB锁时要注意,要将AUTOCOMMIT设为0,否则MySQL不会给表加锁;事务结束前,不要用UNLOCAK TABLES释放表锁,因为UNLOCK TABLES会隐含地提交事务;COMMIT或ROLLBACK产不能释放用LOCAK TABLES加的表级锁,必须用UNLOCK TABLES释放表锁,正确的方式见如下语句。

关于死锁

MyISAM表锁是deadlock free的,这是因为MyISAM总是一次性获得所需的全部锁,要么全部满足,要么等待,因此不会出现死锁。但是在InnoDB中,除单个SQL组成的事务外,锁是逐步获得的,这就决定了InnoDB发生死锁是可能的。

发生死锁后,InnoDB一般都能自动检测到,并使一个事务释放锁并退回,另一个事务获得锁,继续完成事务。但在涉及外部锁,或涉及锁的情况下,InnoDB并不能完全自动检测到死锁,这需要通过设置锁等待超时参数innodb_lock_wait_timeout来解决。需要说明的是,这个参数并不是只用来解决死锁问题,在并发访问比较高的情况下,如果大量事务因无法立即获取所需的锁而挂起,会占用大量计算机资源,造成严重性能问题,甚至拖垮数据库。我们通过设置合适的锁等待超时阈值,可以避免这种情况发生。

通常来说,死锁都是应用设计的问题,通过调整业务流程、数据库对象设计、事务大小、以及访问数据库的SQL语句,绝大部分都可以避免。下面就通过实例来介绍几种死锁的常用方法。

(1)在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序为访问表,这样可以大大降低产生死锁的机会。如果两个session访问两个表的顺序不同,发生死锁的机会就非常高!但如果以相同的顺序来访问,死锁就可能避免。

(2)在程序以批量方式处理数据的时候,如果事先对数据排序,保证每个线程按固定的顺序来处理记录,也可以大大降低死锁的可能。

(3)在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁,而不应该先申请共享锁,更新时再申请排他锁,甚至死锁。

(4)在REPEATEABLE-READ隔离级别下,如果两个线程同时对相同条件记录用SELECTROR UPDATE加排他锁,在没有符合该记录情况下,两个线程都会加锁成功。程序发现记录尚不存在,就试图插入一条新记录,如果两个线程都这么做,就会出现死锁。这种情况下,将隔离级别改成READ COMMITTED,就可以避免问题。

(5)当隔离级别为READ COMMITED时,如果两个线程都先执行SELECTFOR UPDATE,判断是否存在符合条件的记录,如果没有,就插入记录。此时,只有一个线程能插入成功,另一个线程会出现锁等待,当第1个线程提交后,第2个线程会因主键重出错,但虽然这个线程出错了,却会获得一个排他锁!这时如果有第3个线程又来申请排他锁,也会出现死锁。对于这种情况,可以直接做插入 *** 作,然后再捕获主键重异常,或者在遇到主键重错误时,总是执行ROLLBACK释放获得的排他锁。

尽管通过上面的设计和优化等措施,可以大减少死锁,但死锁很难完全避免。因此,在程序设计中总是捕获并处理死锁异常是一个很好的编程习惯。

如果出现死锁,可以用SHOW INNODB STATUS命令来确定最后一个死锁产生的原因和改进措施。

总结

对于MyISAM的表锁,主要有以下几点

(1)共享读锁(S)之间是兼容的,但共享读锁(S)和排他写锁(X)之间,以及排他写锁之间(X)是互斥的,也就是说读和写是串行的。

(2)在一定条件下,MyISAM允许查询和插入并发执行,我们可以利用这一点来解决应用中对同一表和插入的锁争用问题。

(3)MyISAM默认的锁调度机制是写优先,这并不一定适合所有应用,用户可以通过设置LOW_PRIPORITY_UPDATES参数,或在INSERT、UPDATE、DELETE语句中指定LOW_PRIORITY选项来调节读写锁的争用。

(4)由于表锁的锁定粒度大,读写之间又是串行的,因此,如果更新 *** 作较多,MyISAM表可能会出现严重的锁等待,可以考虑采用InnoDB表来减少锁冲突。

对于InnoDB表,主要有以下几点

(1)InnoDB的行销是基于索引实现的,如果不通过索引访问数据,InnoDB会使用表锁。

(2)InnoDB间隙锁机制,以及InnoDB使用间隙锁的原因。

(3)在不同的隔离级别下,InnoDB的锁机制和一致性读策略不同。

(4)MySQL的恢复和复制对InnoDB锁机制和一致性读策略也有较大影响。

(5)锁冲突甚至死锁很难完全避免。

在了解InnoDB的锁特性后,用户可以通过设计和SQL调整等措施减少锁冲突和死锁,包括:

尽量使用较低的隔离级别

精心设计索引,并尽量使用索引访问数据,使加锁更精确,从而减少锁冲突的机会。

选择合理的事务大小,小事务发生锁冲突的几率也更小。

给记录集显示加锁时,最好一次性请求足够级别的锁。比如要修改数据的话,最好直接申请排他锁,而不是先申请共享锁,修改时再请求排他锁,这样容易产生死锁。

不同的程序访问一组表时,应尽量约定以相同的顺序访问各表,对一个表而言,尽可能以固定的顺序存取表中的行。这样可以大减少死锁的机会。

尽量用相等条件访问数据,这样可以避免间隙锁对并发插入的影响。

不要申请超过实际需要的锁级别;除非必须,查询时不要显示加锁。

对于一些特定的事务,可以使用表锁来提高处理速度或减少死锁的可能

对,脏读就是在查询时,有人在更改数据,这样数据就不统一,这是oracle的多版本性(可不是oracle的版本号哦!)和并发性,你可以理解成缓存,就是在没提交之前的临时存放区域,如果你是别的数据库这就需要另解释了,建议你学习一下undo,redo,对你学习oracle绝对有帮助。

SQL语句执行流程与顺序原理解析

Oracle语句执行流程

第一步:客户端把语句发给服务器端执行

当我们在客户端执行SQL语句时,客户端会把这条SQL语句发送给服务器端,让服务器端的进程来处理这语句。也就是说,Oracle 客户端是不会做任何的 *** 作,他的主要任务就是把客户端产生的一些SQL语句发送给服务器端。服务器进程从用户进程把信息接收到后, 在PGA 中就要此进程分配所需内存,存储相关的信息,如:在会话内存存储相关的登录信息等。

虽然在客户端也有一个数据库进程,但是,这个进程的作用跟服务器上的进程作用是不相同的,服务器上的数据库进程才会对SQL 语句进行相关的处理。不过,有个问题需要说明,就是客户端的进程跟服务器的进程是一一对应的。也就是说,在客户端连接上服务器后,在客户端与服务器端都会形成一个进程,客户端上的我们叫做客户端进程,而服务器上的我们叫做服务器进程。

第二步:语句解析

当客户端把SQL语句传送到服务器后,服务器进程会对该语句进行解析。这个解析的工作是在服务器端所进行的,解析动作又可分为很多小动作。

1)查询高速缓存(library cache)

服务器进程在接到客户端传送过来的SQL语句时,不会直接去数据库查询。服务器进程把这个SQL语句的字符转化为ASCII等效数字码,接着这个ASCII码被传递给一个HASH函数,并返回一个hash值,然后服务器进程将到shared pool中的library cache(高速缓存)中去查找是否存在相同的hash值。如果存在,服务器进程将使用这条语句已高速缓存在SHARED POOL的library cache中的已分析过的版本来执行,省去后续的解析工作,这便是软解析。若调整缓存中不存在,则需要进行后面的步骤,这便是硬解析。硬解析通常是昂贵的 *** 作,大约占整个SQL执行的70%左右的时间,硬解析会生成执行树,执行计划,等等。

所以,采用高速数据缓存的话,可以提高SQL 语句的查询效率。其原因有两方面:一方面是从内存中读取数据要比从硬盘中的数据文件中读取数据效率要高,另一方面也是因为避免语句解析而节省了时间。

不过这里要注意一点,这个数据缓存跟有些客户端软件的数据缓存是两码事。有些客户端软件为了提高查询效率,会在应用软件的客户端设置数据缓存。由于这些数据缓存的存在,可以提高客户端应用软件的查询效率。但是,若其他人在服务器进行了相关的修改,由于应用软件数据缓存的存在,导致修改的数据不能及时反映到客户端上。从这也可以看出,应用软件的数据缓存跟数据库服务器的高速数据缓存不是一码事。

2)语句合法性检查(data dict cache)

当在高速缓存中找不到对应的SQL语句时,则服务器进程就会开始检查这条语句的合法性。这里主要是对SQL语句的语法进行检查,看看其是否合乎语法规则。如果服务器进程认为这条SQL语句不符合语法规则的时候,就会把这个错误信息反馈给客户端。在这个语法检查的过程中,不会对SQL语句中所包含的表名、列名等等进行检查,只是检查语法。

3)语言含义检查(data dict cache)

若SQL 语句符合语法上的定义的话,则服务器进程接下去会对语句中涉及的表、索引、视图等对象进行解析,并对照数据字典检查这些对象的名称以及相关结构,看看这些字段、表、视图等是否在数据库中。如果表名与列名不准确的话,则数据库会就会反馈错误信息给客户端。

所以,有时候我们写select语句的时候,若语法与表名或者列名同时写错的话,则系统是先提示说语法错误,等到语法完全正确后再提示说列名或表名错误。

4)获得对象解析锁(control structer)

当语法、语义都正确后,系统就会对我们需要查询的对象加锁。这主要是为了保障数据的一致性,防止我们在查询的过程中,其他用户对这个对象的结构发生改变。

5)数据访问权限的核对(data dict cache)

当语法、语义通过检查之后,客户端还不一定能够取得数据,服务器进程还会检查连接用户是否有这个数据访问的权限。若用户不具有数据访问权限的话,则客户端就不能够取得这些数据。要注意的是数据库服务器进程先检查语法与语义,然后才会检查访问权限。

6)确定最佳执行计划

当语法与语义都没有问题权限也匹配,服务器进程还是不会直接对数据库文件进行查询。服务器进程会根据一定的规则,对这条语句进行优化。在执行计划开发之前会有一步查询转换,如:视图合并、子查询解嵌套、谓语前推及物化视图重写查询等。为了确定采用哪个执行计划,Oracle还需要收集统计信息确定表的访问联结方法等,最终确定可能的最低成本的执行计划。

不过要注意,这个优化是有限的。一般在应用软件开发的过程中,需要对数据库的sql语句进行优化,这个优化的作用要大大地大于服务器进程的自我优化。

当服务器进程的优化器确定这条查询语句的最佳执行计划后, 就会将这条SQL语句与执行计划保存到数据高速缓存(library cache)。如此,等以后还有这个查询时,就会省略以上的语法、语义与权限检查的步骤,而直接执行SQL语句,提高SQL语句处理效率。

第三步:绑定变量赋值

如果SQL语句中使用了绑定变量,扫描绑定变量的声明,给绑定变量赋值,将变量值带入执行计划。若在解析的第一个步骤,SQL在高速缓冲中存在,则直接跳到该步骤。

第四步:语句执行

语句解析只是对SQL语句的语法进行解析,以确保服务器能够知道这条语句到底表达的是什么意思。等到语句解析完成之后,数据库服务器进程才会真正的执行这条SQL语句。

对于SELECT语句:

1)首先服务器进程要判断所需数据是否在db buffer存在,如果存在且可用,则直接获取该数据而不是从数据库文件中去查询数据,同时根据LRU 算法增加其访问计数;

2)若数据不在缓冲区中,则服务器进程将从数据库文件中查询相关数据,并把这些数据放入到数据缓冲区中(buffer cache)。

其中,若数据存在于db buffer,其可用性检查方式为:查看db buffer块的头部是否有事务,如果有事务,则从回滚段中读取数据;如果没有事务,则比较select的scn和db buffer块头部的scn,如果前者小于后者,仍然要从回滚段中读取数据;如果前者大于后者,说明这是一非脏缓存,可以直接读取这个db buffer块的中内容。

对于DML语句(insert、delete、update):

1)检查所需的数据库是否已经被读取到缓冲区缓存中。如果已经存在缓冲区缓存,则直接执行步骤3;

2)若所需的数据库并不在缓冲区缓存中,则服务器将数据块从数据文件读取到缓冲区缓存中;

3)对想要修改的表取得的数据行锁定(Row Exclusive Lock),之后对所需要修改的数据行取得独占锁;

4)将数据的Redo记录复制到redo log buffer;

5)产生数据修改的undo数据;

6)修改db buffer;

7)dbwr将修改写入数据文件;

其中,第2步,服务器将数据从数据文件读取到db buffer经经历以下步骤:

1)首先服务器进程将在表头部请求TM锁(保证此事务执行过程其他用户不能修改表的结构),如果成功加TM锁,再请求一些行级锁(TX锁),如果TM、TX锁都成功加锁,那么才开始从数据文件读数据。

2)在读数据之前,要先为读取的文件准备好buffer空间。服务器进程需要扫描LRU list寻找free db buffer,扫描的过程中,服务器进程会把发现的所有已经被修改过的db buffer注册到dirty list中。如果free db buffer及非脏数据块缓冲区不足时,会触发dbwr将dirty buffer中指向的缓冲块写入数据文件,并且清洗掉这些缓冲区来腾出空间缓冲新读入的数据。

3)找到了足够的空闲buffer,服务器进程将从数据文件中读入这些行所在的每一个数据块(db block)(DB BLOCK是ORACLE的最小 *** 作单元,即使你想要的数据只是DB BLOCK中很多行中的一行或几行,ORACLE也会把这个DB BLOCK中的所有行都读入Oracle DB BUFFER中)放入db buffer的空闲的区域或者覆盖已被挤出LRU list的非脏数据块缓冲区,并且排列在LRU列表的头部,也就是在数据块放入db buffer之前也是要先申请db buffer中的锁存器,成功加锁后,才能读数据到db buffer。

若数据块已经存在于db buffer cache(有时也称db buffer或db cache),即使在db buffer中找到一个没有事务,而且SCN比自己小的非脏缓存数据块,服务器进程仍然要到表的头部对这条记录申请加锁,加锁成功才能进行后续动作,如果不成功,则要等待前面的进程解锁后才能进行动作(这个时候阻塞是tx锁阻塞)。

在记redo日志时,其具体步骤如下:

1)数据被读入到db buffer后,服务器进程将该语句所影响的并被读入db buffer中的这些行数据的rowid及要更新的原值和新值及scn等信息从PGA逐条的写入redo log buffer中。在写入redo log buffer之前也要事先请求redo log buffer的锁存器,成功加锁后才开始写入。

2)当写入达到redo log buffer大小的三分之一或写入量达到1M或超过三秒后或发生检查点时或者dbwr之前发生,都会触发lgwr进程把redo log buffer的数据写入磁盘上的redo file文件中(这个时候会产生log file sync等待事件)。

3)已经被写入redo file的redo log buffer所持有的锁存器会被释放,并可被后来的写入信息覆盖,redo log buffer是循环使用的。Redo file也是循环使用的,当一个redo file写满后,lgwr进程会自动切换到下一redo file(这个时候可能出现log file switch(check point complete)等待事件)。如果是归档模式,归档进程还要将前一个写满的redo file文件的内容写到归档日志文件中(这个时候可能出现log file switch(archiving needed)。

在为事务建立undo信息时,其具体步骤如下:

1)在完成本事务所有相关的redo log buffer之后,服务器进程开始改写这个db buffer的块头部事务列表并写入scn(一开始scn是写在redo log buffer中的,并未写在db buffer)。

2)然后copy包含这个块的头部事务列表及scn信息的数据副本放入回滚段中,将这时回滚段中的信息称为数据块的“前映像”,这个“前映像”用于以后的回滚、恢复和一致性读。(回滚段可以存储在专门的回滚表空间中,这个表空间由一个或多个物理文件组成,并专用于回滚表空间,回滚段也可在其它表空间中的数据文件中开辟)。

在修改信息写入数据文件时,其具体步骤如下:

1)改写db buffer块的数据内容,并在块的头部写入回滚段的地址。

2)将db buffer指针放入dirty list。如果一个行数据多次update而未commit,则在回滚段中将会有多个“前映像”,除了第一个“前映像”含有scn信息外,其他每个"前映像"的头部都有scn信息和"前前映像"回滚段地址。一个update只对应一个scn,然后服务器进程将在dirty list中建立一条指向此db buffer块的指针(方便dbwr进程可以找到dirty list的db buffer数据块并写入数据文件中)。接着服务器进程会从数据文件中继续读入第二个数据块,重复前一数据块的动作,数据块的读入、记日志、建立回滚段、修改数据块、放入dirty list。

3)当dirty queue的长度达到阀值(一般是25%),服务器进程将通知dbwr把脏数据写出,就是释放db buffer上的锁存器,腾出更多的free db buffer。前面一直都是在说明oracle一次读一个数据块,其实oracle可以一次读入多个数据块(db_file_multiblock_read_count来设置一次读入块的个数)

当执行commit时,具体步骤如下:

1)commit触发lgwr进程,但不强制dbwr立即释放所有相应db buffer块的锁。也就是说有可能虽然已经commit了,但在随后的一段时间内dbwr还在写这条sql语句所涉及的数据块。表头部的行锁并不在commit之后立即释放,而是要等dbwr进程完成之后才释放,这就可能会出现一个用户请求另一用户已经commit的资源不成功的现象。

2)从Commit和dbwr进程结束之间的时间很短,如果恰巧在commit之后,dbwr未结束之前断电,因为commit之后的数据已经属于数据文件的内容,但这部分文件没有完全写入到数据文件中。所以需要前滚。由于commit已经触发lgwr,这些所有未来得及写入数据文件的更改会在实例重启后,由smon进程根据重做日志文件来前滚,完成之前commit未完成的工作(即把更改写入数据文件)。

3)如果未commit就断电了,因为数据已经在db buffer更改了,没有commit,说明这部分数据不属于数据文件。由于dbwr之前触发lgwr也就是只要数据更改,(肯定要先有log)所有dbwr在数据文件上的修改都会被先一步记入重做日志文件,实例重启后,SMON进程再根据重做日志文件来回滚。

其实smon的前滚回滚是根据检查点来完成的,当一个全部检查点发生的时候,首先让LGWR进程将redologbuffer中的所有缓冲(包含未提交的重做信息)写入重做日志文件,然后让dbwr进程将dbbuffer已提交的缓冲写入数据文件(不强制写未提交的)。然后更新控制文件和数据文件头部的SCN,表明当前数据库是一致的,在相邻的两个检查点之间有很多事务,有提交和未提交的。

当执行rollback时,具体步骤如下:

服务器进程会根据数据文件块和db buffer中块的头部的事务列表和SCN以及回滚段地址找到回滚段中相应的修改前的副本,并且用这些原值来还原当前数据文件中已修改但未提交的改变。如果有多个”前映像“,服务器进程会在一个“前映像”的头部找到“前前映像”的回滚段地址,一直找到同一事务下的最早的一个“前映像”为止。一旦发出了commit,用户就不能rollback,这使得commit后dbwr进程还没有全部完成的后续动作得到了保障。

第五步:提取数据

当语句执行完成之后,查询到的数据还是在服务器进程中,还没有被传送到客户端的用户进程。所以,在服务器端的进程中,有一个专门负责数据提取的一段代码。他的作用就是把查询到的数据结果返回给用户端进程,从而完成整个查询动作。

从这整个查询处理过程中,我们在数据库开发或者应用软件开发过程中,需要注意以下几点:

一是要了解数据库缓存跟应用软件缓存是两码事情。数据库缓存只有在数据库服务器端才存在,在客户端是不存在的。只有如此,才能够保证数据库缓存中的内容跟数据库文件的内容一致。才能够根据相关的规则,防止数据脏读、错读的发生。而应用软件所涉及的数据缓存,由于跟数据库缓存不是一码事情,所以,应用软件的数据缓存虽然可以提高数据的查询效率,但是,却打破了数据一致性的要求,有时候会发生脏读、错读等情况的发生。所以,有时候,在应用软件上有专门一个功能,用来在必要的时候清除数据缓存。不过,这个数据缓存的清除,也只是清除本机上的数据缓存,或者说,只是清除这个应用程序的数据缓存,而不会清除数据库的数据缓存。

二是绝大部分SQL语句都是按照这个处理过程处理的。我们DBA或者基于Oracle数据库的开发人员了解这些语句的处理过程,对于我们进行涉及到SQL语句的开发与调试,是非常有帮助的。有时候,掌握这些处理原则,可以减少我们排错的时间。特别要注意,数据库是把数据查询权限的审查放在语法语义的后面进行检查的。所以,有时会若光用数据库的权限控制原则,可能还不能满足应用软件权限控制的需要。此时,就需要应用软件的前台设置,实现权限管理的要求。而且,有时应用数据库的权限管理,也有点显得繁琐,会增加服务器处理的工作量。因此,对于记录、字段等的查询权限控制,大部分程序涉及人员喜欢在应用程序中实现,而不是在数据库上实现。

Oracle SQL语句执行顺序

(8)SELECT (9) DISTINCT (11) <select_list>

(1) FROM <left_table>

(3) <join_type> JOIN <right_table>

(2) ON <join_condition>

(4) WHERE <where_condition>

(5) GROUP BY <group_by_list>

(6) WITH {CUBE | ROLLUP}

(7) HAVING <having_condition>

(10) ORDER BY <order_by_list>

1)FROM:对FROM子句中的表执行笛卡尔积(交叉联接),生成虚拟表VT1。

2)ON:对VT1应用ON筛选器,只有那些使为真才被插入到TV2。

3)OUTER (JOIN):如果指定了OUTER JOIN(相对于CROSS JOIN或INNER JOIN),保留表中未找到匹配的行将作为外部行添加到VT2,生成TV3。如果FROM子句包含两个以上的表,则对上一个联接生成的结果表和下一个表重复执行步骤1到步骤3,直到处理完所有的表位置。

4)WHERE:对TV3应用WHERE筛选器,只有使为true的行才插入TV4。

5)GROUP BY:按GROUP BY子句中的列列表对TV4中的行进行分组,生成TV5。

6)CUTE|ROLLUP:把超组插入VT5,生成VT6。

7)HAVING:对VT6应用HAVING筛选器,只有使为true的组插入到VT7。

8)SELECT:处理SELECT列表,产生VT8。

9)DISTINCT:将重复的行从VT8中删除,产品VT9。

10)ORDER BY:将VT9中的行按ORDER BY子句中的列列表顺序,生成一个游标(VC10),生成表TV11,并返回给调用者。

以上每个步骤都会产生一个虚拟表,该虚拟表被用作下一个步骤的输入。这些虚拟表对调用者(客户端应用程序或者外部查询)不可用。只有最后一步生成的表才会会给调用者。如果没有在查询中指定某一个子句,将跳过相应的步骤。

jdbc驱动

java jdbc连接_2019最新JDBC面试题(附JDBC教程)

weixin_39665379

关注

0点赞·410人阅读

1什么是JDBC API,何时使用它?

Java DataBase Connectivity API允许我们使用关系数据库。JDBC API接口和类是part javasql和javaxsqlpackage的一部分。我们可以使用JDBC API来获取数据库连接,在数据库服务器中运行SQL查询和存储过程并处理结果。

JDBC API的编写方式允许我们的Java程序和实际的JDBC驱动程序之间的松散耦合,这使我们可以轻松地从一个数据库切换到另一个数据库服务器。

2JDBC驱动程序有哪几种类型?

有四种类型的JDBC驱动程序。任何与数据库一起工作的java程序都有两个部分,第一部分是JDBC API,第二部分是执行实际工作的驱动程序。

JDBC-ODBC Bridge加ODBC驱动程序(类型1):它使用ODBC驱动程序连接到数据库。我们应该安装ODBC驱动程序来连接数据库,这就是为什么这个驱动程序几乎已经过时的原因。

Native API部分支持Java技术的驱动程序(类型2):此驱动程序将JDBC类转换为数据库服务器的客户端API。我们应该安装数据库客户端API。由于对数据库客户端API驱动程序的额外依赖性,这也不是首选驱动程序。

用于数据库中间件的纯Java驱动程序(类型3):此驱动程序将JDBC调用发送到可以连接到不同类型数据库的中间件服务器。我们应该安装一个中间件服务器来使用这个驱动程序。这增加了额外的网络调用和性能降低,这就是为什么不广泛使用JDBC驱动程序。

直接到数据库的纯Java驱动程序(类型4):此驱动程序将JDBC调用转换为数据库服务器可以理解的网络协议。该解决方案简单,适用于网络上的数据库连接。但是对于此解决方案,我们应该使用特定于数据库的驱动程序,例如Oracle for Oracle DB的OJDBC jar和MySQL Connector的MySQL Connector / J

3JDBC API如何帮助我们实现Java程序和JDBC驱动程序API之间的松散耦合?

JDBC API使用Java Reflection API实现java程序和JDBC驱动程序之间的松散耦合。如果你看一个简单的JDBC例子,你会发现所有的编程都是用JDBC API完成的,而驱动程序只有在通过反射使用ClassforName()方法加载时才会出现。

我认为这是在核心java类中使用Reflection的最佳示例之一,以确保我们的应用程序不能直接使用Drivers API,这使得从一个数据库移动到另一个数据库变得非常容易。

4什么是JDBC连接?解释在简单的java程序中获取数据库连接的步骤。

JDBC Connection就像使用数据库服务器创建的Session。您还可以将Connection 视为来自数据库服务器的Socket连接。

创建JDBC连接非常简单,需要两个步骤:

注册并加载驱动程序:使用ClassforName(),驱动程序类注册到DriverManager并加载到内存中。

使用DriverManager获取Connection对象:我们DriverManagergetConnection()通过传递数据库URL字符串,用户名和密码作为参数来获取连接对象。

Connection

5JDBC DriverManager类有什么用?

JDBC DriverManager是我们通过它获取数据库连接对象的工厂类。当我们加载JDBC Driver类时,它将自己注册到DriverManager,您可以查找JDBC Driver类源代码来检查它。

当我们通过传递数据库配置细节来调用方法DriverManagergetConnection()时,DriverManager使用已注册的驱动程序来获取Connection并将其返回给调用者程序。

6如何在java程序中获取数据库服务器的详细信息?

我们可以使用DatabaseMetaDataobject来获取数据库服务器的详细信息。成功创建数据库连接后,我们可以通过调用getMetaData()方法获取元数据对象。我们可以使用DatabaseMetaData中的方法来获取数据库产品名称,版本和详细的配置信息。

DatabaseMetaData

7什么是JDBC Statement?

JDBC API Statement用于在数据库中执行SQL查询。我们可以通过调用Connection createStatement()方法来创建Statement对象。我们可以使用Statement通过不同的执行方法传递查询来执行静态SQL查询,例如execute(),executeQuery(),executeUpdate()等。

由于查询是在java程序中生成的,如果未正确验证用户输入,则可能导致SQL注入问题,可以在SQL注入示例中找到更多详细信息。

默认情况下,每个Statement对象只能同时打开一个ResultSet对象。因此,如果我们想要使用多个ResultSet对象,则每个对象必须由不同的Statement对象生成。Statement接口中的所有execute()方法都隐式关闭一个statment的当前ResultSet对象(如果存在open对象)。

8execute,executeQuery,executeUpdate有什么区别?

Statement execute(String query)用于执行任何SQL查询,如果结果是ResultSet(如运行Select查询),则返回TRUE。当没有ResultSet对象(如运行Insert或Update查询)时,输出为FALSE。我们可以使用getResultSet()获取ResultSet和getUpdateCount()方法来检索更新计数。

Statement executeQuery(String query)用于执行Select查询并返回ResultSet。即使没有与查询匹配的记录,返回的ResultSet也永远不会为null。执行select查询时,我们应该使用executeQuery方法,这样如果有人试图执行insert / update语句,它将抛出javasqlSQLException,并显示消息“executeQuery方法不能用于更新”。

语句executeUpdate(String query)用于执行不返回任何内容的Insert / Update / Delete(DML)语句或DDL语句。输出为int,等于SQL数据 *** 作语言(DML)语句的行数。对于DDL语句,输出为0。

只有在不确定语句类型时才应使用execute()方法,否则使用executeQuery或executeUpdate方法。

9什么是JDBC PreparedStatement?

JDBC PreparedStatement对象表示预编译的SQL语句。我们可以使用它的setter方法来设置查询的变量。

由于PreparedStatement是预编译的,因此可以使用它多次有效地执行此语句。PreparedStatement是Statement的更好选择,因为它会自动转义特殊字符并避免SQL注入攻击。

10如何在JDBC PreparedStatement中设置NULL值?

我们可以使用PreparedStatement setNull()方法将null变量绑定到参数。例如,setNull方法将index和SQL Types作为参数 pssetNull(10, javasqlTypesINTEGER);。

11Statement中的getGeneratedKeys()方法有什么用?

有时,表可以使用自动生成的键来插入主键的唯一列值。我们可以使用Statement getGeneratedKeys()方法获取此自动生成密钥的值。

12PreparedStatement对Statement有什么好处?

PreparedStatement对Statement的一些好处是:

PreparedStatement帮助我们防止SQL注入攻击,因为它会自动转义特殊字符。

PreparedStatement允许我们使用参数输入执行动态查询。

PreparedStatement比Statement快。当我们重用PreparedStatement或使用它的批处理方法执行多个查询时,它变得更加明显。

PreparedStatement帮助我们使用setter方法编写面向对象的代码,而使用Statement我们必须使用String Concatenation来创建查询。如果要设置多个参数,则使用字符串连接编写查询看起来非常难看并且容易出错。

13PreparedStatement的限制是什么以及如何克服它?

PreparedStatement的一个限制是我们不能直接在IN子句中使用它。将PreparedStatement与IN子句一起使用的一些替代方法是:

执行单一查询 性能非常慢,不推荐使用

使用存储过程 特定于数据库,因此不适用于多个数据库应用程序。

动态创建PreparedStatement查询 良好的方法但失去了缓存的PreparedStatement的好处。

在PreparedStatement查询中使用NULL 当您知道变量输入的最大数量时,这是一种很好的方法,可以通过部分执行来扩展以允许无限制的参数。可以在JDBC PreparedStatement IN子句替代中找到更详细的分析。

14什么是JDBC ResultSet?

JDBC ResultSet就像一个表示数据库结果集的数据表,通常通过执行查询数据库的语句来生成。

ResultSet对象维护指向其当前数据行的游标。最初,光标位于第一行之前。next()方法将光标移动到下一行。如果没有更多行,则next()方法返回false,并且可以在while循环中使用它来迭代结果集。

默认的ResultSet对象不可更新,并且只有一个向前移动的游标。因此,您只能迭代一次,并且只能从第一行到最后一行。可以使用以下语法生成可滚动和/或可更新的ResultSet对象。

Statement stmt = concreateStatement(ResultSetTYPE_SCROLL_INSENSITIVE,

ResultSetCONCUR_UPDATABLE);

当生成它的Statement对象关闭,重新执行或用于从多个结果序列中检索下一个结果时,ResultSet对象将自动关闭。

我们可以使用ResultSet getter方法,列名或索引号从1开始检索列数据。

15ResultSet有哪些不同的类型?

在创建Statement时,我们可以根据用户输入获得不同类型的ResultSet对象。如果您将查看Connection方法,您将看到createStatement()和prepareStatement()方法被重载以提供ResultSet类型和并发作为输入参数。

ResultSet对象有三种类型。

ResultSetTYPEFORWARDONLY:这是默认类型,游标只能在结果集中向前移动。

ResultSetTYPESCROLLINSENSITIVE:游标可以前后移动,结果集对创建结果集后其他人对数据库所做的更改不敏感。

ResultSetTYPESCROLLSENSITIVE:游标可以向前和向后移动,结果集对创建结果集后其他人对数据库所做的更改很敏感。基于并发性,有两种类型的ResultSet对象。

ResultSetCONCURREADONLY:结果集是只读的,这是默认的并发类型。

ResultSetCONCUR_UPDATABLE:我们可以使用ResultSet更新方法来更新行数据。

16Statement中的setFetchSize()和setMaxRows()方法有什么用?

我们可以使用setMaxRows(int i)方法来限制数据库从查询返回的行数。您可以使用SQL查询本身实现相同的功能。例如,在MySQL中,我们可以使用LIMIT子句来设置查询返回的最大行数。

理解fetchSize可能很棘手,因为你应该知道Statement和ResultSet是如何工作的。当我们在数据库中执行查询时,将在数据库缓存中获取并维护结果,并返回ResultSet。ResultSet是具有对数据库中结果的引用的游标。

假设我们有一个返回100行的查询,并且我们将fetchSize设置为10,因此在每次数据库访问时,JDBC驱动程序将只获取10行,因此将有10次访问以获取所有行。如果每行需要大量处理时间并且结果中的行数很大,那么设置最佳fetchSize会很有帮助。

我们可以通过Statement对象设置fetchSize,但可以通过ResultSet对象setFetchSize()方法覆盖它。

17如何使用JDBC API调用存储过程?

存储过程是一组SQL查询,它们在数据库中编译,可以从JDBC API执行。JDBC CallableStatement可用于在数据库中执行存储过程。初始化CallableStatement的语法是;

CallableStatement

//我们需要在调用存储过程之前注册外部参数

stmt

我们需要在执行CallableStatement之前注册OUT参数。

18什么是JDBC批处理,它有什么好处?

有时我们需要为数据库运行类似的批量查询,例如将数据从CSV文件加载到关系数据库表。我们知道我们可以选择使用Statement或PreparedStatement来执行查询。除此之外,JDBC API还提供了批处理功能,通过该功能,我们可以一次性为数据库执行大量查询。

JDBC API支持通过Statement和PreparedStatement addBatch()以及executeBatch()方法进行批处理。

批处理比一次执行一个语句更快,因为数据库调用的数量较少。

19什么是JDBC事务管理?我们为什么需要它?

默认情况下,当我们创建数据库连接时,它以自动提交模式运行。这意味着无论何时执行查询并完成查询,都会自动触发提交。因此,我们触发的每个SQL查询都是一个事务,如果我们运行一些DML或DDL查询,则每个SQL语句完成后,更改都会保存到数据库中。

有时我们希望一组SQL查询成为事务的一部分,以便我们可以在所有查询运行正常时提交它们,如果我们得到任何异常,我们可以选择回滚作为事务的一部分执行的所有查询。

JDBC API提供了一种方法,setAutoCommit(boolean flag)通过该方法我们可以禁用连接的自动提交功能。我们应该仅在需要时禁用自动提交,因为除非我们在连接上调用commit()方法,否则不会提交事务。数据库服务器使用表锁来实现事务管理,这是资源密集型过程。所以我们应该在完成交易后立即提交交易。

20如何回滚JDBC事务?

我们可以使用Connection对象rollback()方法来回滚事务。它将回滚事务所做的所有更改,并释放此Connection对象当前持有的所有数据库锁。

21什么是JDBC Savepoint?如何使用它?

有时,事务可以是多个语句的组,我们希望回滚到事务中的特定点。JDBC Savepoint帮助我们在事务中创建检查点,并且我们可以回滚到该特定检查点。

为事务创建的任何保存点都会自动释放,并在提交事务时或在回滚整个事务时变为无效。将事务滚动回保存点会自动释放并使在相关保存点之后创建的任何其他保存点无效。

22什么是JDBC DataSource?它的好处是什么?

JDBC DataSource是javaxsql包中定义的接口,它比DriverManager更强大,可用于数据库连接。我们可以使用DataSource来创建数据库连接,而Driver实现类可以用来获取连接的实际工作。除了获取数据库连接外,DataSource还提供了一些其他功能,例如:

缓存PreparedStatement以加快处理速度 连接超时设置 记录功能 ResultSet最大大小阈值 使用JNDI支持在servlet容器中连接池 在JDBC DataSource上阅读有关DataSource的更多信息。

23如何在Apache Tomcat Server中使用JDBC DataSource和JNDI实现JDBC连接池?

对于部署在servlet容器中的Web应用程序,创建JDBC连接池非常简单,只需要几个步骤。

在容器配置文件中创建JDBC JNDI资源,通常是serverxml或contextxml。例如

server

在Web应用程序中,使用InitialContext查找在第一步中配置的JNDI资源,然后获取连接。

Context

24什么是Apache DBCP API?

如果您使用DataSource获取数据库连接,通常用于获取连接的代码与特定于驱动程序的DataSource实现紧密耦合。除了选择DataSource实现类之外,大多数代码都是样板代码。

Apache DBCP通过提供DataSource实现来帮助我们摆脱这些问题,DataSource实现充当我们的程序和不同JDBC驱动程序之间的抽象层。Apache DBCP库依赖于Commons Pool库,因此请确保它们都在构建路径中。

25什么是JDBC连接隔离级别?

当我们使用JDBC事务来实现数据完整性时,DBMS使用锁来阻止其他人访问事务所访问的数据。DBMS使用锁来防止脏读,不可重复读和幻像读问题。

DBMS使用JDBC事务隔离级别来使用锁定机制,我们可以通过Connection getTransactionIsolation()方法获取隔离级别信息,并使用setTransactionIsolation()方法设置它。

26什么是JDBC RowSet?RowSet有哪些类型?

JDBC RowSet以更灵活的方式保存表格数据,即ResultSet。所有RowSet对象都是从ResultSet派生的,因此它们具有ResultSet的所有功能以及一些其他功能。RowSet接口在javaxsql包中定义。

RowSet提供的一些附加功能包括:

具有属性的Java Bean及其getter-setter方法。RowSet使用JavaBeans事件模型,它们可以向任何已注册的组件发送通知,用于事件,例如光标移动,更新/插入/删除行以及更改为RowSet内容。

默认情况下,RowSet对象是可滚动和可更新的,因此如果DBMS不支持可滚动或可更新的ResultSet,我们可以使用RowSet来获取这些功能。

RowSet大致分为两种类型:

已连接的RowSet对象 - 这些对象连接到数据库,与ResultSet对象最相似。JDBC API仅提供一个连接的RowSet对象javaxsqlrowsetJdbcRowSet,它的标准实现类是comsunrowsetJdbcRowSetImpl 断开连接的RowSet对象 - 这些RowSet对象不需要连接到数据库,因此它们更轻量级且可序列化。它们适合通过网络发送数据。有四种类型的断开连接的RowSet实现。

CachedRowSet - 它们可以获取连接并执行查询并读取ResultSet数据以填充RowSet数据。我们可以在数据断开连接时 *** 作和更新数据,并重新连接到数据库并写入更改。WebRowSet派生自CachedRowSet - 它们可以读写XML文档。JoinRowSet派生自WebRowSet - 它们可以形成SQL JOIN而无需连接到数据源。从WebRowSet派生的FilteredRowSet - 我们可以应用过滤条件,以便只有选定的数据可见。

27ResultSet和RowSet有什么不同?

RowSet对象派生自ResultSet,因此它们具有ResultSet的所有功能以及一些附加功能。RowSet的一大好处是它们可以断开连接,使其轻量级,并且易于通过网络传输。

是否使用ResultSet或RowSet取决于您的要求,但如果您计划使用ResultSet更长的持续时间,则断开连接的RowSet是释放数据库资源的更好选择。

28常见的JDBC异常有哪些?

一些常见的JDBC异常是:

javasqlSQLException - 这是JDBC异常的基本异常类。javasqlBatchUpdateException - 当Batch *** 作失败时抛出此异常,但它依赖于JDBC驱动程序是否抛出此异常或基本SQLException。javasqlSQLWarning - 用于SQL *** 作中的警告消息。javasqlDataTruncation - 当数据值因超出MaxFieldSize而被意外截断时。

29JDBC中的CLOB和BLOB数据类型是什么?

字符大对象(CLOB)是由具有关联代码页的单字节字符组成的字符串。此数据类型适用于存储面向文本的信息,其中信息量可能超出常规VARCHAR数据类型的限制(上限为32K字节)。

二进制大对象(BLOB)是由字节组成的二进制字符串,没有关联的代码页。此数据类型可以存储大于VARBINARY(32K限制)的二进制数据。此数据类型适用于存储图像,语音,图形和其他类型的业务或特定于应用程序的数据。

30什么是JDBC中的“脏读”?哪个隔离级别可防止脏读?

当我们处理事务时,有可能更新行,同时其他查询可以读取更新的值。这会导致脏读,因为更新后的值不是永久性的,已更新行的事务可以回滚到先前的值,从而导致无效数据。

隔离级别TRANSACTIONREADCOMMITTED,TRANSACTION_REPEATABLEREAD和TRANSACTIONSERIALIZABLE阻止了脏读。

31什么是2阶段提交?

当我们在涉及多个数据库的分布式系统中工作时,我们需要使用2阶段提交协议。2阶段提交协议是分布式系统的原子承诺协议。在第一阶段,事务管理器向所有事务资源发送commit-request。如果所有事务资源都正常,则事务管理器将为所有资源提交事务更改。如果任何事务资源响应为Abort,则事务管理器可以回滚所有事务更改。

32JDBC中有哪些不同类型的锁定?

从广义上讲,有两种类型的锁定机制可以防止数据损坏,因为多个用户使用相同的数据。

乐观锁定 - 使用代码实现此锁定。表中引入了一个额外的列以保持更新计数。当您选择该行时,您也会阅读此列,比如“version”。现在,当您尝试更新/删除行时,将在where子句中传递此“version”。因此,如果在其间执行其他线程的更新,则更新将失败。这是避免数据损坏的好方法,但如果有人错过更新其更新语句中的“version”,则可能容易出错。通过这种锁定方式,更新查询看起来如下所示。

mysql> update emp SET name = ‘David’, version = 5 WHERE id = 10 and version = 4;

悲观锁定 - 将记录从选择锁定到读取,更新和提交阶段。这通常由数据库供应商软件完成,并通过使用SELECT FOR UPDATE查询触发。如果线程处理锁定较长时间,则这种锁定行的方式可能导致性能降低和死锁。

除此之外,一些DBMS系统提供锁定机制来锁定单行,表或数据库。

33你对DDL和DML语句有什么了解?

数据定义语言(DDL)语句用于定义数据库模式。创建,更改,删除,截断,重命名语句属于DDL语句,通常它们不返回任何结果。

数据 *** 作语言(DML)语句用于 *** 纵数据库模式中的数据。选择,插入,更新,删除,调用等是DML语句的示例。

34javautilDate和javasqlDate有什么区别?

javautilDate包含有关日期和时间的信息,而javasqlDate包含仅有关日期的信息,它没有时间信息。因此,如果您必须在数据库中保留时间信息,建议使用Timestamp或DateTime字段。

35如何将图像或原始数据插入数据库?

我们可以使用BLOB将图像或原始二进制数据插入数据库。

36什么是幻像读取以及哪种隔离级别阻止了它?

虚拟读取是指事务多次执行查询并获取不同数据的情况。假设事务正在执行查询以根据条件获取数据,然后另一个事务插入与条件匹配的行。现在,当同一事务再次执行查询时,新行将成为结果集的一部分。这个新行被称为Phantom Row,这种情况称为Phantom Read。

只有使用TRANSACTION_SERIALIZABLE隔离级别才能阻止幻像读取。

37什么是SQL警告?如何在JDBC程序中检索SQL警告?

SQLWarning是SQLException的子类,我们可以通过在Connection,Statement和ResultSet对象上调用getWarnings()方法来检索它。SQL警告不会停止脚本的执行,但会警告用户警告。

38如何使用数据库对象作为IN / OUT调用Oracle存储过程?

如果Oracle存储过程具有作为数据库对象的IN / OUT参数,那么我们需要在程序中创建相同大小的Object数组,然后使用它来创建Oracle STRUCT对象。然后我们可以通过调用setSTRUCT()方法为数据库对象设置此STRUCT对象并使用它。

39我们什么时候得到javasqlSQLException:找不到合适的驱动程序?

当SQL URL字符串格式不正确时,您将得到没有合适的驱动程序发现异常。您可以在使用DriverManager的简单Java应用程序或使用DataSource的JNDI资源中获取此异常。异常堆栈跟踪如下所示。

org

在调试此异常时,只需检查日志中打印的URL,如上面的日志中URL URL是’jdbc:mysql:// localhost:3306 / UserDB,而它应该是jdbc:mysql:// localhost:3306 / UserDB 。

40JDBC有哪些最佳实践?

一些JDBC最佳实践是:

数据库资源很重,因此请确保在完成后立即关闭它。Connection,Statement,ResultSet和所有其他JDBC对象都定义了close()方法来关闭它们。

始终在代码中显式关闭结果集,语句和连接,因为如果您在连接池环境中工作,则可能会将连接返回到池,从而导致打开的结果集和语句对象导致资源泄漏。

关闭finally块中的资源,以确保即使在异常情况下它们也会关闭。

使用批处理进行类似的批量 *** 作。

始终对Statement使用PreparedStatement以避免SQL注入并获得PreparedStatement的预编译和缓存优势。

如果要将批量数据检索到结果集中,则为fetchSize设置最佳值有助于获得良好的性能。

数据库服务器可能不支持所有隔离级别,因此请在假设之前进行检查。

更严格的隔离级别会导致性能降低,因此请确保为数据库连接设置了最佳隔离级别。

如果要在Web应用程序中创建数据库连接,请尝试使用JNDI上下文使用JDBC DataSource资源来重用连接。

当您需要长时间使用ResultSet时,请尝试使用断开连接的RowSet。

相对来说net安全性会高一些,net代码最后都是封装成dll文件的,这个是对于编程语言来说的,

如果写成程序后哪个更安全,那主要看写程序的人,

如果程序员牛B,那么他用哪个写,哪个就安全!

以上就是关于SQL语句执行过程详解全部的内容,包括:SQL语句执行过程详解、关于MySQL中的表锁和行锁、脏读是读了没有提交到数据库中数据我懂但等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zz/10102784.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-05
下一篇 2023-05-05

发表评论

登录后才能评论

评论列表(0条)

保存