操作系统I/O基础知识

  只是想找本简单的书来试试英文,本以为《JavaNio》会够简单(单从API上来看),结果第一章的关于操作系统I/O的知识给跪了,查了很多wiki也没完全搞清楚,话说回来这些知识也不是一天能搞定的,特别是针对高级语言开发者。
  很幸运,虽然理解不透切,并且可能有理解有误差的地方,但学到了很多概念,至少算是把I/O方面知识图谱完善了一下。

  在操作系统IO中,有以下几个基本的概念:
    Buffer
    Vectored I/O
    Virtual Memory
    Page
    File I/O
    Stream I/O & Non Blocking Model

Buffer

  Buffer是IO操中最基础的部分,所有的输入/输出操作都就是将数据从buffer移进或者移出的动作。
  当进程/程序需要读取一个外部数据到进程内存时,进程首先向kernel发出read()的系统调用(System Call),kernel则调用Disk Controller,通过DMA将数据,无需借助CPU,直接从disk写入到kerner space中buffer。当kernel内的buffer写入完毕后则将这个buffer中的数据复制到用户进程内的buffer中。
  如下图所示:
buffer
  之所以不直接将数据写入到进程内存的原因如下:
    1.user space不能访问硬件,而kernel space是操作系统的使用的内存,有权限直接访问硬件。
    2.另一方面,硬件也不能使用访问虚拟内存地址(Process中使用的是虚拟内存地址,后面会讲到)。
    2.读取面向块的存储设备时,用户进程请求的读入非对称的数据块,需要kernel进行组装与拆解。

  所有的IO操作都需要经过kernel,无论是直接的或者间接的。 阅读全文

Btree索引详解

Btree索引(或Balanced Tree),是一种很普遍的数据库索引结构,oracle默认的索引类型(本文也主要依据oracle来讲)。其特点是定位高效、利用率高、自我平衡,特别适用于高基数字段,定位单条或小范围数据非常高效。理论上,使用Btree在亿条数据与100条数据中定位记录的花销相同。

数据结构利用率高、定位高效

Btree索引的数据结构如下:

btree_structure

 

结构看起来Btree索引与Binary Tree相似,但在细节上有所不同,上图中用不同颜色的标示出了Btree索引的几个主要特点:

  • 树形结构:由根节(root)、分支(branches)、叶(leaves)三级节点组成,其中分支节点可以有多层。
  • 多分支结构:与binary tree不相同的是,btree索引中单root/branch可以有多个子节点(超过2个)。
  • 双向链表:整个叶子节点部分是一个双向链表(后面会描述这个设计的作用)
  • 单个数据块中包括多条索引记录

这里先把几个特点罗列出来,后面会说到各自的作用。

结构上Btree与Binary Tree的区别,在于binary中每节点代表一个数值,而balanced中root和Btree节点中记录了多条”值范围”条目(如:[60-70][70-80]),这些”值范围”条目分别指向在其范围内的叶子节点。既root与branch可以有多个分支,而不一定是两个,对数据块的利用率更高
阅读全文

科普:事务隔离级别与误读

很久之前写的一篇文章,只是简单的叙述

先回顾一下事务的属性(ACID原则)

原子性 一个事务应该是一个原子操作,要么成功,要么失败,不能存在部分成功部分失败的中间状态。
一致性 事务无论成败不能破坏业务规则,必须是从一个一致性状态到另一个一致性状态。(如转帐:钱的总量不能变化/一致)
隔离性 每个事务必须独立于其它事务执行。
持久性 执行结果的值/状态持久。

 

数据并发操作的三种误读

脏读(Dirty Read)

打开一个”别人”正读写的资源的数据。既能读到未提交的数据。
不可重复读(Nonrepeatable Read)
在一个原子操作中,时间点t1去读取一个值与时间点t2再次读取这个值发生了变化,可以是更新,删除。
幻象读(Phantom Read)
在时间点t1执行一个查询,得到的结果集与在时间点t2执行相同查询的结果集不一样,与不可重复读有点类似,不过不可重复读强调的同一个数据在随时间变化,而幻象读强调的是两次查询[结果集行数]增加。而不是指[数据值]的变化。

根据三种读允许出现的情况,正好可以划分ANSI/ISO SQL规定的四种隔离级别

隔离级别 脏读 不可重复读 幻像读
Read Uncommit(未提交读) 允许 允许 允许
Read Commit(提交读) 禁止 允许 允许
Repeatable Read(可重复读) 禁止 禁止 允许
Serializable(串行读) 禁止 禁止 禁止

阅读全文

禁用高变更频率表的统计信息

前一段时间,稳定运行了两个月的系统,其中一个功能突然变得非常慢,平常只需要零点零几秒完成的操作,需要等上百秒。一度导致系统无法使用影响生产。通过查看AWR报告、查看历史执行计划,发现一条Oracle为一条SQL选用了完全错误的执行计划:连接方式、索引使用情况和数据量预判都完全错误。开始认为是由于统计信息过期,就手动收集了这张表的统计信息。系统也立即恢复了正常。我当时判断这是一个偶然事件,但没有料到刚过了一周,同样的问题再次发生了。

虽然解决方案简单但由于影响严重,决定跟踪一下代码。发现这个操作所涉及到关联一张变更频率非常高的表,仅几分钟到几十分钟,这张表里的数据就完全变样。Oracle默认的统计信息收集频率应该是一天一次,大大低于表的变更频率。对于这个问题有两种方案:

  • 一是等到表张表里的数据分布有代表性时,手动将这张表的统计信息锁住,之后每次查询都使用相同的统计信息。
  • 二是利用oracle动态样例收集(Dynamic Sampling)的特性:清空这张表中所有的统计信息、相关索引的统计信息并锁住。当oracle执行语句前,若发现这张表没有可用的统计信息,则会按照配置参数,对这张表数据做一次采集,以生成较优的执行计划。

阅读全文

数据库分页优化

线上发现一个分页页面较之前明显变慢,随即对这个功能进行了优化。优化的过程很简单,但从这个问题中引出一个较经典的分页问题,需要以培训听形式向组员传播、打疫苗,因为这个问题在开发阶段会往往被忽视掉,却能引发严重的后果。

“ 简单优化”

SELECT *
  FROM (SELECT WMMATDOCSA0_.*, ROWNUM R
           FROM (SELECT WMMATDOCSA0_.*
                    FROM TI_WM_MAT_DOC_SAP_WM WMMATDOCSA0_
                   WHERE NOT EXISTS (SELECT 1
                            FROM TT_WM_MAT_DOC_HEADER MATDOCHEAD1_
                           WHERE TO_CHAR(MATDOCHEAD1_.WM_YEAR_NO) || '-' ||
                                 MATDOCHEAD1_.WM_MAT_DOC_NO =
                                 WMMATDOCSA0_.WM_DOC_NO)
                   ORDER BY WMMATDOCSA0_.ID DESC) )
 WHERE R > 1 AND R < 15 

该SQL使用一个not exists子句作为主要的查询条件,这类SQL的性能严重依赖于exists内子句的性能(外层有多少条数据,exists子句就要执行多少次)。由于TT_WM_MAT_DOC_HEADER数据的增长,内层子句的性能已经明显降低,导致整个SQL效率急剧下降。系统中这条SQL的时间需要使用2秒左右。

方案一:增加索引,提升子句性能
注意SQL第7-9行:

TO_CHAR(MATDOCHEAD1_.WM_YEAR_NO)||'-'||MATDOCHEAD1_.WM_MAT_DOC_NO
              = WMMATDOCSA0_.WM_DOC_NO

这个过滤条件是一个没有索引的函数化列,随着数据量的增加这个过滤条件的效率会降低。可以在这里建立一个函数索引,提高exists子句的执行效率从而提高整个语句的效率。(若exists子句性能慢0.1秒,外层数据有1000条,则总共慢100秒,当然oracle会根据统计信息选择优化的执行计划,这里只是举个例子)

CREATE INDEX FX_MAT_DOC_NO_YEAR ON 
       TT_WM_MAT_DOC_HEADER(to_char(WM_YEAR_NO)||'-'||WM_MAT_DOC_NO);

此外exsit子句有“短路”的特性,一旦匹配到记录就中断返回。整个语句的执行效率相当不错。
阅读全文