客户端SQL Plus请求连接,监听接受客户端的TCP连接,并获取客户端发过来的TNS数据包。
监听进程打开用于与子进程通信的管道,同时fork一个子进程,称为“监听子进程1”的子进程,然后监听进程一直等待,直到这个“监听子进程1”结束。
监听子进程1 Fork出子进程2。
完成上面一步,子进程1马上退出并结束子进程1。
子进程2收集本进程所在的主机名、IP地址及进程号等信息,并把子进程2重名成server process(这里我们也把server process叫前台进程或叫服务器进程),申请占用一小块PGA内存。
前台进程把主机名、IP地址及进程号发送给监听进程。
监听进程收到前台进程的信息,并返回客户端的信息(比如用户密码环境变量等)给前台进程。
前台进程查询USER$、PROFILE$等数据字典,校验用户名密码是否合法,如果用户密码错误就报错用户名密码无效,否则就与客户端进行交互。
客户端收到前台进程的信息与之交互,整个连接创建完成。
客户端发起一个连接,服务器端的监听创建一个shadow process,并把这个影子进程指派给用户,后期用户所有的操作都提交给shadow,shadow会替用户完成数据库内的操作,并把结果返回给用户。(中间件有点不同)
客户端发起update语句,根据客户端环境字符集转换成对应的编码,传到服务器端,
数据库端会的server进程,与用户进程组成一个会话。然后在server端的pga区,处理sql请求。服务器端根据服务器端字符集转换。
process接受语句,放在PGA中,将修改前的值放在PGA
在用户的private sql area区域,每个字符包括空格转化成ASCII码后,再拿这一堆ASCII码通过HASH函数生成一个sql_hash值
确定这条语句的执行计划
搜索当前用户的session缓存中(在PGA中的UGA)是否存在相同的散列版本。
如果存在,则直接通过游标link到位于PGA的private SQL AREA( private SQL area),此时成为软软解析。
如果不存在,检查初始化参数SESSION_CACHED_CURSORS是否被设置,如果被设置,且有相同的sql,则同样可以通过游标指向到位于PGA的私有SQL AREA,否则,结束软软解析,尝试软解析。
如果不存在,创建一个游标。拿这个值去shared pool中找执行计划,根据hash值,判断这个块应该在哪个桶中。
从内存中申请一个lock structure,在其中记录“锁模式,进程ID”等重要信息
然后看能否立刻获得资源访问权,如果不能,则把这个lock structure 挂到resource structure的waiter链表中,如果能获得,则把lock structure 挂到resource structure的owner链表中。
锁定数据块头链表,逐个遍历上面的块头,看有没有与这条语句相同的哈希值
如果有,如果检查到共享库中有一个语句具有相同的哈希值,则数据库执行语义和环境检查, 以确定其含义是否相同。即使两个语句在语义上是相同的,某个环境差异也可能使其强制进行硬解 析。在这种情况下,环境是可以影响执行计划生成的全部会话设置, 如工作区大小或优化器设置等。取出,执行软解析
否则,执行硬解析
检查sql的语法、语义、权限,
查询相关的数据字典
根据CBO或者RBO生成执行计划
CBO,直方图,动态采样
Oracle首先扫描shared pool的freelist中是否有足够空间,如果有则使用,如果没有足够的空间,则判断此次内存请求是一次large请求,还是一次small请求 。
若是large请求,则在reserved pool 查找是否有可用的空间,如果找到了可用的内存(chunk)则作size检查,并对内存(chunk)做截断操作,截取所需内存大小使用,如果在reserved pool 中依然没有找到可用的内存(chunk),会重复上一步,如果依然没有找到,则对reserved pool 中的对象做LRU算法的age out操作,age out一些reserved pool 中的对象,来满足本次的内存(chunk)请求操作,如果还是没有找到可用的内存,重复LRU算法,直到找到可用内存(chunk)。
若是small请求,则在shared pool 的free list中查找是否有可用的内存(chunk),如果找到可用的内存(chunk)则作size检查,并对内存(chunk)做截断操作,截取所需内存大小使用,如果没有找到,则对shared pool中的对象做LRU算法的age out操作,并再次查找是否有可用的内存,知道找到可用的chunk
shared pool
查找空闲内存,如果没有发现大小正好合适的空闲chunk,就查找更大的chunk,如果找到比请求的大小更大的空闲chunk,则将它分裂,写入执行计划,挂到library cache的链上,多余部分继续放到空闲列表中。因为过多的硬解析加剧了内存段分配的需求,这样就产生了碎片问题。系统经过长时间运行后,就会产生大量小的内存碎片。当请求分配一个较大的内存块时,尽管shared pool总空闲空间还很大,但是没有一个单独的连续空闲块能满足需要。这时,就可能产生 ORA-4031错误。
完成硬解析
完成解析
如果使用了,绑定变量,将实际的变量值代入SQL语句中。
读取要修改的数据块。
在dbbuffer中找要修改的块
查询SEG$等数据字典,找到要修改表段头
从段头读出Extent Map,按执行计划开始扫描数据块
将块所在的file#,block#哈希算计算后,找相应hash bucket,获得保护这个bucket的cbc latch
遍历桶中所有CBC链。获得cache buffers chains latch,遍历那条buffer chain直到找到需要的buffer header。此时需要注意,如果执行计划是走全表扫描,或者是带有唯一约束的索引(update),是以独占模式获取CBC链,会引起CBC链busy。
比较buffer header上所记录的数据块的地址(rdba),如果不符合,则跳过该buffer header。
跳过状态为CR的buffer header。(说明有别的进程正在进行一致性读,所以才构造了这个cr块,如果我也要找这个块的原块,我需要自己再重新构造一个新的cr块,不会使用这个旧的cr块,如果我不是找这个块的原块,那我不需要构造,所以这两种情况下都是跳过cr块)
如果遇到状态为READING(正在从磁盘上读出的数据块)的buffer header,则等待,一直等到该buffer header的状态改变以后再比较所记录的数据块的地址是否符合。(说不定是之前的查询,有可能就是这条sql语句,也有可能是之前的(自己用户或者其他用户的sql)语句,正好也需要读这个块内的数据,正在往内存里读,这下我就可以直接用前辈的努力就可以了)
block在内存中。如果发现数据块地址符合的buffer header,则查看该buffer header是否位于正在使用的列表上,如果是位于正在使用的列表上,则判断已存在的锁定模式与当前所要求的锁定模式是否兼容,如果是兼容的,则返回该buffer header所记录的数据块地址,并将当前进程号放入该buffer header所处的正在使用的列表上
根据需要进行的操作类型(读或写),它需要在buffer header上获得一个共享或独占模式的buffer pin或者buffer lock
若进程获得buffer header pin,它会释放获得的cache buffers chains latch,然后执行对buffer block的操作,若进程无法获得buffer header pin,它就会在buffer busy waits事件上等待。(进程之所以无法获得buffer header pin,是因为为了保证数据的一致性,同一时刻一个block只能被一个进程pin住进行存取,因此当一个进程需要存取buffer cache中一个被其他进程使用的block的时候,这个进程就会产生对该block的buffer busy waits事件。)
在段头块上还是目标块?加上TM、TX锁,获取目标块的latch,通过块头找到块在内存中的地址,
如果有锁的冲突,则产生阻塞。
wait方式
pin :当一个会话无法获得需要的latch时,会继续使用CPU(CPU空转),达到一个间隔后,再次尝试申请latch,直到达到最大的重试次数。
sleep:当 一个会话无法获得需要的latch时,会等待一段时间(sleep),达到一个间隔后,再次尝试申请latch,如此反复,直到达到最大的重试次数。
no wait方式
不会发生sleep或者spin., 转而去获取其它可用的Latch
如果是干净块,修改完后,挂到ckpt链上,在LRU链上的使用次数+1,(特定条件后)移动到LRUW上,并添加到检查点链
block在硬盘。 如果比较完整个hash chain以后还没发现所要找的buffer header,则从磁盘上读取数据文件。
会话产生一个shadow process将块读到内存,
在读取数据之前,Server进程需要扫描辅助LRU List寻找Free的Buffer,扫描过程中Server进程会把发现的所有已经被修改过的Buffer注册到LRUW List上
如果Server进程扫描LRU超过一个阀值仍然不能找到足够的Free Buffer,将停止寻找,转而通知DBWn去写出脏数据,释放内存空间。这时进程会处于free buffer wait等待
(非全表扫描)找到足够的Buffer之后,Server进程就可以将Buffer从数据文件读入Buffer Cache,并将buffer移动到主LRU,如果非全表扫描较多,辅助LRU中块会越来越少,为了保持比例(辅助LRU占整个LRU总块数的20%到25%左右),SMON进程会3秒一次持有LRU
Latch,将主LRU冷端末的块移到辅助LRU。全表扫描也先到辅助LRU中寻找可用块,但全表扫描的块仍将留在辅助LRU,不会调往主LRU冷端头。因此全表扫描的块将很快被覆盖。全表
扫描操作将只使用辅助LRU(也会用到主LRU,只会用到很少量的主LRU),一次大的全扫操作,可以将辅助LRU的所有块覆盖一遍或多遍。数据库刚启动时,或刚Flush Buffer_cache时,所有块会被放在辅助LRU中。前台进程(服务器进程)扫描主、辅LRU时,会将遇到的TCH为2以下的脏块,移到主LRUW。
将块所在的file#,block#哈希算计算后,找相应hash bucket,获得保护这个bucket的cbc latch
将读取到的数据块所对应的buffer header挂到CBC(cache buffer chain)链上,块放到对应链的buffer上,头的地址指向链上的对应的buffer块。
物理/逻辑一致性检查。(计算这个数据块的校验和,并和块头的字段相比较,如果有差异,oracle就知道这个块有错误,会报出ORA-1578错误,会对数据块做cache recovery,如果不能把数据块恢复到一致状态,oracle会把这个数据块标志位software corrupt。如果是其他错误,则需要使用dbms_repair包吧数据块标识为“software corrupt”,块头的校验和字段在写回磁盘前会进行重新计算。执行与否受参数db_block_checksum影响)
Dbv只检查数据块的header/footer,做逻辑验证;
Db_block_checking:替代10210/10211/10212事件,进行块完整性检查,如free slot list/行位置/锁数量;检查时会复制块,如有错误将块标志为soft corruption;
Db_block_checksum:dbwr和direct loader写数据块时计算checksum并存于cache层chkval,再次读取时重新计算并与已有checksum比较;
Dbms_repair修复cache/transaction层的错误,将块标示为soft corruption;
对数据块进行逻辑一致性检查。检查失败会抛出ORA-1578的internal错误。当oracle检查到数据块的逻辑一致性时,会对数据块做cache recovery,如果不能把数据块恢复到一致状态,oracle会把这个数据块标志位software corrupt,当有查询访问到这个数据块时,也会抛出ora-1578错误。如果不是抛出ORA-1578,则需要先使用dbms_repair包。
先进行一致读,找出要修改的行
在LRU链的冷端找一个干净的块,将数据块放到其中,如果寻找达到一定数目的块,还未找到干净块,则将LRU链冷端的脏块,迁移到LRUW链上,?
释放cbc latch
检查要修改的行上有没有锁标记
如果有,根据对应的事务槽,找到相应的回滚段,看事务表中记录的事务是否提交
如果提交,去处所标记,将对应的修改事务槽事务状态为U,进行下一步。(C=Commited;U=Commited Upper Bound;T=Active at CSC;---是未提交 )
如果回滚段事务表中事务信息被覆盖,则认为事务已经提交,去处锁标记,将对应的修改事务槽事务状态为U,进行下一步。
如果未提交,则等待其提交,产生buffer busy 等待事件
如果没有,进行下一步。
将该BH的标记设置为钉住(ping)
检查有没有before类型触发器,如果有执行(在此过程中:new的值可能会被重新赋值)。
随后,oracle以当前模式读这个块,如果查找这一行的(where)条件列已经修改过。由于使用条件列来定位这条记录,而且条件列已经修改,所以数据库会重启查询。
从shared pool中分配的一块内存划分给一个private stand,并受到redo allocation latch的保护,这个事务生成的redo存放在private stand中,当flush private stand 或者commit时,private stand被批量写入log文件中,对于使用Private strand的事务,无需先申请Redo Copy Latch,也无需申请Shared Strand的redo allocation latch,而是flush或commit是批量写入磁盘,因此减少了Redo Copy Latch和redo allocation latch申请/释放次数、也减少了这些latch的等待,从而降低了CPU的负荷。(redo log 最开始是在pga中的uga产生的(数据库一般是专有模式),oracle会把它拷贝到SGA中的log_buffer中去,如果log_buffer过小,或者lgwr不能够快速将redo 写入到log file中,那么就会产生log buffer space等待事件,遇到此类问题,可以增加 log_buffer大小,调整log file 到裸设备,I/0快的磁盘中)
在PGA中生成DataBlock块的后映像(11.9)。
在PGA中生成UNDO段头事务表的后映像(5.2)。
在PGA中生成UNDO块的后映像(5.1)
将前三个Redo矢量做为一条Redo Recorder写入Shared pool中的Private strand。
将DataBlock中的前映像值,写入Shared pool中的Imu pool。
修改UNDO段头的事务表。
修改UNDO块,写入DataBlock的前映像。
将每条还原改变向量写到对应的IMU池,或者依照旧的机制;
将每条重做改变向量写到私有redo去;
如果新事务申请不到private stand的redo allocation latch,则会继续遵循旧的redo buffer机制,申请写入shared strand中。
在PGA中构建change vector并组合成redo record
在PGA中生成UNDO段头事务表的后映像(5.2)
在PGA中生成UNDO块的后映像(5.1)
在PGA中生成DataBlock块的后映像(11.9)
将前三个Redo矢量做为一条Redo Recorder写入Log buffer
修改UNDO段头的事务表,事务正式开始。
修改UNDO块,写入DataBlock的前映像。
改变这个块
取消block 的pin标记
修改DataBlock,将新值写入Buffer cache。
调用kcrfwr()将record写入log buffer:
计算record占用的空间大小;分配SCN;
获取copy latch,验证SCN;
获取allocation latch,检验log buffer/file是否有足够空间,有则释放allocation latch将redo写入log buffer,否则同时释放allocation/copy latch并通知LGWR进行log flush/switch;(为防止多个进程同时通知LGWR刷新redo或切换日志文件,引入write latch(只有1个),只有获取此latch后才能进行下一步操作;)
将redo record写至log buffer而后释放copy latch,检查是否达到触发LGWR阈值;
更新BH对应的内存数据块的内容。
更新BH对应的内存数据块的内容。
do块
志先记录到PGA中,再写回undo
commit;
为事务生成一个scn,lgwr将所有余下的缓存重做日志条目写至磁盘,并把scn记录到在线重做日志文件中。
事务条目从v$transaction中删除
v$lock中记录会话所持有的锁,全部释放,等待排队这些锁的每个事务将会被唤醒,继续完成他们的工作
如果事务修改的某些块还在缓冲区缓存中,则会以一种快速的模式访问并“清理”(指清除存储在数据库块首部与锁相关的信息。
全表扫描时出现单块读:当表中有一些块存在时,全表扫描,会跳过这些块,这也是在全表扫描时,出现单块读的原因,还有一种情况是,到区的边界,也有可能出现单块读。欲成佛,先成莫
XID 回滚段,段的文件编号,块号,行号,使用次数
update慢的因素:
经常update的列不宜采用索引(体系结构P237)
尽量不要使用触发器,会使update变慢
触发器中尽量不要使用before for each row,没有after for each row 高效(体系结构P238)