| Profil de 孔雀Glazy MazePhotosBlogListes | Aide |
|
|
IIS7中的isapi redirect 1.2.X过滤器配置(连接IIS和AJP)IIS7中的isapi redirect 1.2.X过滤器配置(连接IIS和AJP)
extension_uri=/jakarta/isapi_redirect.dll log_file=D:\Applications\tomcat\connector\iis_jk\isapi_redirect.log log_level=info worker_file=D:\Applications\tomcat\connector\iis_jk\workers.properties worker_mount_file=D:\Applications\tomcat\connector\iis_jk\workers_mapping.properties worker.list=svc,dev,status #status worker worker.status.type=status #load balance worker #worker.lb.type=lb #worker.lb.balance_workers=dev1,dev2 #service worker worker.svc.type=ajp13 worker.svc.host=localhost worker.svc.port=9009 #devel worker worker.dev.type=ajp13 worker.dev.host=localhost worker.dev.port=8009 /song/*.jsp=dev /song/*.jspa=dev /jira/*=svc /jk=status
虚拟目录名称对应isapi_redirect.properties中的extension_uri参数。使IIS能通过extension_uri访问到ispai_redirect.dll。
并给该虚拟目录增加执行权限:
真正的牛叉山寨~山寨版………………CPU!!! 架构——看着纯忽悠 cpu——我晕~~ 整机——太牛了…… 可以玩游戏——点点点~~~ 作者——admire到死~~ 这就是美国加州游戏开发人员Steve Chamberlin向我们展示的至高境界的DIY。从2007年起,他就开始了“BMOW”项目(Big Mess of Wires/一堆乱麻),目标是使用加法器、计数器、或非元件、触发器等标准零部件,制作一个8位处理器,相当于二十世纪八十年代初的水平,最近终于完成了第一个作品“BMOW-1”——以后也许还会有二号、三号…… 既然是手工作品,就不可能像我们常用的微处理器那样把数十亿个晶体管集成在几百平方毫米的空间内。事实上,BMOW-1的面积约有450平方厘米,是个极其复杂的大块头:基础是一块Augat绕线板,之上已经预先装好了1250个针脚,需要手工给它们镀金,然后连接2500条线,有些地方甚至要堆叠十层(可以想象一下铜互连)。Chamberlin说他最快可以每小时做25个这样的连接,也就是全部完成至少得100个小时,不吃不喝不睡觉也得干四天多。 当然这不但是个技术活,也需要金钱做后盾,单单是材料费就花了大概3000美元(得镀金呢)。 BMOW-1处理器目前的运行频率是2MHz,理论上可以跑到3MHz。虽然只相当于现代微处理器的零头,但已经可以胜任很多工作了,比如Basic语言编程,比如玩玩吃豆子、国际象棋之类的小游戏。 其它主要规格:
其实这个牛得一塌糊涂的手工处理器在今年五月底就已完成,Chamberlin还带着它参加了一年一度的创意大会Maker Faire,自然是博得满堂彩。今天我们旧事重提,就是和Intel的工业级流水线对比一下,让大家细细欣赏一遍,也证明那句广告词:Impossible is nothing! 图太多了,大家自己去看吧 http://news.mydrivers.com/1/139/139180.htm继续接上文101 More Great Computer QuotesComputing
Knowledge
Users
Internet
Professionals
Programming
Development
Quality
Programming Languages
Security
Companies
Predictions
接上文101 Great Computer Programming Quotes"People always fear change. People feared electricity when it was invented, didn't they? People feared coal, they feared gas-powered engines. There will always be ignorance, and ignorance leads to fear. But with time, people will come to accept their silicon masters." As Bill Gates once warned, computers have indeed become our silicon masters, pervading nearly every aspect of our modern lives. As a result, some of the greatest minds of our time have pondered the significance of computers and software on the human condition. Following are 101 great quotes about computers, with an emphasis on programming, since after all this is a software development site. Computers
Computer Intelligence
Trust
Hardware
Software
Operating Systems
Internet
Software Industry
Software Demos
Software Patents
Complexity
Ease of Use
Users
Programmers
Programming
Programming Languages
C/C++"
Java
Open Source
Code
Software Development
Debugging
Quality
Predictions
语录:101条伟大的计算机编程名言“人们总是害怕改变。电被发明出来的时候他们害怕电,是不是?他们害怕煤,害怕蒸汽机车。无知无所不在,并导致恐惧。但随着时间推移,人们终究会接受最新的科技。” 正如比尔盖茨曾经警告过一样,计算机已经真正成为我们的最新科技,几乎遍布我们日常生活的每一方面。所以,我们这个时代的某些最伟大的头脑开始思索起计算机和软件对于人类的重要性来了。以下就是101条有关计算机的伟大名言,并且,既然我们这个网站是一个软件开发网站,我们尤其关注编程方面的。
计算机大型网站架构演变和知识体系 zz这篇文章很实用,尤其在自己白手起家搞网站的时候 之前也有一些介绍大型网站架构演变的文章,例如LiveJournal的、ebay的,都是非常值得参考的,不过感觉他们讲的更多的是每次演变的结果,而没有很详细的讲为什么需要做这样的演变,再加上近来感觉有不少同学都很难明白为什么一个网站需要那么复杂的技术,于是有了写这篇文章的想法,在这篇文章中将阐述一个普通的网站发展成大型网站过程中的一种较为典型的架构演变历程和所需掌握的知识体系,希望能给想从事互联网行业的同学一点初步的概念,文中的不对之处也请各位多给点建议,让本文真正起到抛砖引玉的效果。 架构演变第一步:物理分离webserver和数据库 最开始,由于某些想法,于是在互联网上搭建了一个网站,这个时候甚至有可能主机都是租借的,但由于这篇文章我们只关注架构的演变历程,因此就假设这个时候已经是托管了一台主机,并且有一定的带宽了,这个时候由于网站具备了一定的特色,吸引了部分人访问,逐渐你发现系统的压力越来越高,响应速度越来越慢,而这个时候比较明显的是数据库和应用互相影响,应用出问题了,数据库也很容易出现问题,而数据库出问题的时候,应用也容易出问题,于是进入了第一步演变阶段:将应用和数据库从物理上分离,变成了两台机器,这个时候技术上没有什么新的要求,但你发现确实起到效果了,系统又恢复到以前的响应速度了,并且支撑住了更高的流量,并且不会因为数据库和应用形成互相的影响。 看看这一步完成后系统的图示:
这一步涉及到了这些知识体系: 这一步架构演变对技术上的知识体系基本没有要求。 架构演变第二步:增加页面缓存 好景不长,随着访问的人越来越多,你发现响应速度又开始变慢了,查找原因,发现是访问数据库的操作太多,导致数据连接竞争激烈,所以响应变慢,但数据库连接又不能开太多,否则数据库机器压力会很高,因此考虑采用缓存机制来减少数据库连接资源的竞争和对数据库读的压力,这个时候首先也许会选择采用squid等类似的机制来将系统中相对静态的页面(例如一两天才会有更新的页面)进行缓存(当然,也可以采用将页面静态化的方案),这样程序上可以不做修改,就能够很好的减少对webserver的压力以及减少数据库连接资源的竞争,OK,于是开始采用squid来做相对静态的页面的缓存。 看看这一步完成后系统的图示:
这一步涉及到了这些知识体系: 前端页面缓存技术,例如squid,如想用好的话还得深入掌握下squid的实现方式以及缓存的失效算法等。 架构演变第三步:增加页面片段缓存 增加了squid做缓存后,整体系统的速度确实是提升了,webserver的压力也开始下降了,但随着访问量的增加,发现系统又开始变的有些慢了,在尝到了squid之类的动态缓存带来的好处后,开始想能不能让现在那些动态页面里相对静态的部分也缓存起来呢,因此考虑采用类似ESI之类的页面片段缓存策略,OK,于是开始采用ESI来做动态页面中相对静态的片段部分的缓存。 看看这一步完成后系统的图示:
这一步涉及到了这些知识体系: 页面片段缓存技术,例如ESI等,想用好的话同样需要掌握ESI的实现方式等; 架构演变第四步:数据缓存 在采用ESI之类的技术再次提高了系统的缓存效果后,系统的压力确实进一步降低了,但同样,随着访问量的增加,系统还是开始变慢,经过查找,可能会发现系统中存在一些重复获取数据信息的地方,像获取用户信息等,这个时候开始考虑是不是可以将这些数据信息也缓存起来呢,于是将这些数据缓存到本地内存,改变完毕后,完全符合预期,系统的响应速度又恢复了,数据库的压力也再度降低了不少。 看看这一步完成后系统的图示:
这一步涉及到了这些知识体系: 缓存技术,包括像Map数据结构、缓存算法、所选用的框架本身的实现机制等。 架构演变第五步:增加webserver 好景不长,发现随着系统访问量的再度增加,webserver机器的压力在高峰期会上升到比较高,这个时候开始考虑增加一台webserver,这也是为了同时解决可用性的问题,避免单台的webserver down机的话就没法使用了,在做了这些考虑后,决定增加一台webserver,增加一台webserver时,会碰到一些问题,典型的有: 1、如何让访问分配到这两台机器上,这个时候通常会考虑的方案是Apache自带的负载均衡方案,或LVS这类的软件负载均衡方案; 2、如何保持状态信息的同步,例如用户session等,这个时候会考虑的方案有写入数据库、写入存储、cookie或同步session信息等机制等; 3、如何保持数据缓存信息的同步,例如之前缓存的用户数据等,这个时候通常会考虑的机制有缓存同步或分布式缓存; 4、如何让上传文件这些类似的功能继续正常,这个时候通常会考虑的机制是使用共享文件系统或存储等; 在解决了这些问题后,终于是把webserver增加为了两台,系统终于是又恢复到了以往的速度。 看看这一步完成后系统的图示:
这一步涉及到了这些知识体系: 负载均衡技术(包括但不限于硬件负载均衡、软件负载均衡、负载算法、linux转发协议、所选用的技术的实现细节等)、主备技术(包括但不限于ARP欺骗、linux heart-beat等)、状态信息或缓存同步技术(包括但不限于Cookie技术、UDP协议、状态信息广播、所选用的缓存同步技术的实现细节等)、共享文件技术(包括但不限于NFS等)、存储技术(包括但不限于存储设备等)。 架构演变第六步:分库 享受了一段时间的系统访问量高速增长的幸福后,发现系统又开始变慢了,这次又是什么状况呢,经过查找,发现数据库写入、更新的这些操作的部分数据库连接的资源竞争非常激烈,导致了系统变慢,这下怎么办呢,此时可选的方案有数据库集群和分库策略,集群方面像有些数据库支持的并不是很好,因此分库会成为比较普遍的策略,分库也就意味着要对原有程序进行修改,一通修改实现分库后,不错,目标达到了,系统恢复甚至速度比以前还快了。 看看这一步完成后系统的图示:
这一步涉及到了这些知识体系: 这一步更多的是需要从业务上做合理的划分,以实现分库,具体技术细节上没有其他的要求; 但同时随着数据量的增大和分库的进行,在数据库的设计、调优以及维护上需要做的更好,因此对这些方面的技术还是提出了很高的要求的。 架构演变第七步:分表、DAL和分布式缓存 随着系统的不断运行,数据量开始大幅度增长,这个时候发现分库后查询仍然会有些慢,于是按照分库的思想开始做分表的工作,当然,这不可避免的会需要对程序进行一些修改,也许在这个时候就会发现应用自己要关心分库分表的规则等,还是有些复杂的,于是萌生能否增加一个通用的框架来实现分库分表的数据访问,这个在ebay的架构中对应的就是DAL,这个演变的过程相对而言需要花费较长的时间,当然,也有可能这个通用的框架会等到分表做完后才开始做,同时,在这个阶段可能会发现之前的缓存同步方案出现问题,因为数据量太大,导致现在不太可能将缓存存在本地,然后同步的方式,需要采用分布式缓存方案了,于是,又是一通考察和折磨,终于是将大量的数据缓存转移到分布式缓存上了。 看看这一步完成后系统的图示:
这一步涉及到了这些知识体系: 分表更多的同样是业务上的划分,技术上涉及到的会有动态hash算法、consistent hash算法等; DAL涉及到比较多的复杂技术,例如数据库连接的管理(超时、异常)、数据库操作的控制(超时、异常)、分库分表规则的封装等; 架构演变第八步:增加更多的webserver 在做完分库分表这些工作后,数据库上的压力已经降到比较低了,又开始过着每天看着访问量暴增的幸福生活了,突然有一天,发现系统的访问又开始有变慢的趋势了,这个时候首先查看数据库,压力一切正常,之后查看webserver,发现apache阻塞了很多的请求,而应用服务器对每个请求也是比较快的,看来是请求数太高导致需要排队等待,响应速度变慢,这还好办,一般来说,这个时候也会有些钱了,于是添加一些webserver服务器,在这个添加webserver服务器的过程,有可能会出现几种挑战: 1、Apache的软负载或LVS软负载等无法承担巨大的web访问量(请求连接数、网络流量等)的调度了,这个时候如果经费允许的话,会采取的方案是购买硬件负载,例如F5、Netsclar、Athelon之类的,如经费不允许的话,会采取的方案是将应用从逻辑上做一定的分类,然后分散到不同的软负载集群中; 2、原有的一些状态信息同步、文件共享等方案可能会出现瓶颈,需要进行改进,也许这个时候会根据情况编写符合网站业务需求的分布式文件系统等; 在做完这些工作后,开始进入一个看似完美的无限伸缩的时代,当网站流量增加时,应对的解决方案就是不断的添加webserver。 看看这一步完成后系统的图示:
这一步涉及到了这些知识体系: 到了这一步,随着机器数的不断增长、数据量的不断增长和对系统可用性的要求越来越高,这个时候要求对所采用的技术都要有更为深入的理解,并需要根据网站的需求来做更加定制性质的产品。 架构演变第九步:数据读写分离和廉价存储方案 突然有一天,发现这个完美的时代也要结束了,数据库的噩梦又一次出现在眼前了,由于添加的webserver太多了,导致数据库连接的资源还是不够用,而这个时候又已经分库分表了,开始分析数据库的压力状况,可能会发现数据库的读写比很高,这个时候通常会想到数据读写分离的方案,当然,这个方案要实现并不容易,另外,可能会发现一些数据存储在数据库上有些浪费,或者说过于占用数据库资源,因此在这个阶段可能会形成的架构演变是实现数据读写分离,同时编写一些更为廉价的存储方案,例如BigTable这种。 看看这一步完成后系统的图示:
这一步涉及到了这些知识体系: 数据读写分离要求对数据库的复制、standby等策略有深入的掌握和理解,同时会要求具备自行实现的技术; 廉价存储方案要求对OS的文件存储有深入的掌握和理解,同时要求对采用的语言在文件这块的实现有深入的掌握。 架构演变第十步:进入大型分布式应用时代和廉价服务器群梦想时代 经过上面这个漫长而痛苦的过程,终于是再度迎来了完美的时代,不断的增加webserver就可以支撑越来越高的访问量了,对于大型网站而言,人气的重要毋庸置疑,随着人气的越来越高,各种各样的功能需求也开始爆发性的增长,这个时候突然发现,原来部署在webserver上的那个web应用已经非常庞大了,当多个团队都开始对其进行改动时,可真是相当的不方便,复用性也相当糟糕,基本是每个团队都做了或多或少重复的事情,而且部署和维护也是相当的麻烦,因为庞大的应用包在N台机器上复制、启动都需要耗费不少的时间,出问题的时候也不是很好查,另外一个更糟糕的状况是很有可能会出现某个应用上的bug就导致了全站都不可用,还有其他的像调优不好操作(因为机器上部署的应用什么都要做,根本就无法进行针对性的调优)等因素,根据这样的分析,开始痛下决心,将系统根据职责进行拆分,于是一个大型的分布式应用就诞生了,通常,这个步骤需要耗费相当长的时间,因为会碰到很多的挑战: 1、拆成分布式后需要提供一个高性能、稳定的通信框架,并且需要支持多种不同的通信和远程调用方式; 2、将一个庞大的应用拆分需要耗费很长的时间,需要进行业务的整理和系统依赖关系的控制等; 3、如何运维(依赖管理、运行状况管理、错误追踪、调优、监控和报警等)好这个庞大的分布式应用。 经过这一步,差不多系统的架构进入相对稳定的阶段,同时也能开始采用大量的廉价机器来支撑着巨大的访问量和数据量,结合这套架构以及这么多次演变过程吸取的经验来采用其他各种各样的方法来支撑着越来越高的访问量。 看看这一步完成后系统的图示:
这一步涉及到了这些知识体系: 这一步涉及的知识体系非常的多,要求对通信、远程调用、消息机制等有深入的理解和掌握,要求的都是从理论、硬件级、操作系统级以及所采用的语言的实现都有清楚的理解。 运维这块涉及的知识体系也非常的多,多数情况下需要掌握分布式并行计算、报表、监控技术以及规则策略等等。 说起来确实不怎么费力,整个网站架构的经典演变过程都和上面比较的类似,当然,每步采取的方案,演变的步骤有可能有不同,另外,由于网站的业务不同,会有不同的专业技术的需求,这篇blog更多的是从架构的角度来讲解演变的过程,当然,其中还有很多的技术也未在此提及,像数据库集群、数据挖掘、搜索等,但在真实的演变过程中还会借助像提升硬件配置、网络环境、改造操作系统、CDN镜像等来支撑更大的流量,因此在真实的发展过程中还会有很多的不同,另外一个大型网站要做到的远远不仅仅上面这些,还有像安全、运维、运营、服务、存储等,要做好一个大型的网站真的很不容易,写这篇文章更多的是希望能够引出更多大型网站架构演变的介绍。 枯燥的东西 zz素材来自《猫头鹰与上帝的对话》,主要是一些围绕基督教上帝的思辨性讨论,全文没头没尾,也肯定有整理不当乃至于错误的地方。所有的讨论早就烂大街了,反正整理完了就发出来。 全能者(Omnipotence)拉丁单词,Omni即全部,potence即能够。上帝能否造出自己也举不起的石头?由此引发对“不可能”的思考: 一、逻辑意义上的不可能方的圆,大于整体的部分……上帝能否突破逻辑的法则?强的回答是,可以;折中的回答是,逻辑是上帝神圣属性或本质的一部分;弱的回答是不可以。设想这样一个问题:上帝能自杀么? 二、时限意义上的不可能
以上第二小点实质上是一种道德意义上的不可能。 三、道德意义上的不可能上帝能残暴地杀害一个善良的人么?强的回答是,可以——造物主有权杀死任何被造物,例如天降洪水淹死了诺亚方舟外的任何人——包括无辜的婴儿。另一个角度的说法是,不应该随意用人类的道德来批判上帝。 四、共相上的不可能柏拉图认为,造物者(demiurge)洞察了先在的蓝图、形式或说共相(universals),并把先在的质料(matter)纳入其中,这就是创造的过程。柏拉图说的当然不是基督教中的上帝,但可以给我们以启示。无论从什么角度来理解“可能性”,深层次的思考是,究竟是上帝创造了可能性,还是先在的可能性限制着上帝。 全知者(Omniscience)用反逻辑、反事实的荒谬问题(四条边的三角形如何定义?明天下午的三倍是多少?儿子通常比父亲大几岁?)来拷问上帝也许有点过分了,全知也许意味着上帝知道任何具有可知性的事情。虽然很多命题我们可以用无意义或者谬误来打发,但对于“本句话是假的”这句话到底是真是假这样的问题,不知上帝有何见教。但这类责难或许也不够有力,人类自己就“有可能”通过某些思考来解决或者消解掉悖论(多值逻辑、分层论……),更不用说上帝了。因此,还是关注一下更常见的辩论吧: 一、自由问题通常的说法是,“上帝赋予了人类自由意志”。如果全知的上帝能够预测任何人的任何行为,这句话中的“自由”在上帝眼中还成其为“自由”么?人类曾经因罪触怒上帝降下灾祸,如果上帝早就预知了人类的罪恶并且这些人还都是祂自己造的,祂有啥好怒的?或者设想:上帝是一个忘情投入的剧作家,被自己创作出的人物和情节深深触动、大喜大悲。 二、超越时间的上帝通常的说法是,上帝是外在于时间的,古往今来在上帝眼中统统表现为“永恒的现在 ”(Eternal Now)。立刻产生一个问题:如果一个先天的盲人无法理解色彩的话,外在于时间的上帝能够“感受、理解、知道”何谓时间么?祂能够真正了解和知道人类的“ 时间感”(覆水难收的过去、扑朔迷离的未来、沧桑的记忆、伤感的怀旧……)么?剧作家创作了一切,但只要他从不亲自上台表演自己写的剧本,就无法体会演员的种种。上帝是如此地关注、关爱着世界并且有所作为,因此理应在时间之内。似乎只能这么解释了——上帝想在时间外就可以在时间外,想在时间内就可以在时间内。 至善者(Ominibenevolent)至善的根本表现就是上帝之爱——既不是君王居高临下之爱,也不是对等回报式的爱,而是真正的博爱。希腊文中eros之爱,主要指因为对象的可爱性质所激发的爱心,然而上帝之爱是agape,普遍、无条件、主动给予、永不转移的爱,归根结底,上帝就是爱。 这部分的辩论主要围绕道德标准来进行——人类通常理解的道德是非观,能往上帝身上套吗?或者上帝才是道德是非的最高、最终、唯一标准?依仗人类的道德观,很容易发现上帝尤其是旧约中的耶和华有邪恶色彩。将上帝纳入世俗道德评判体系,明显降格了上帝的地位。因此正统神学观点都倾向于说,上帝才是道德的判据。“ 因为虐杀一个善良的人是恶的,上帝不会做这样的事情”,这句话置上帝于人类道德观之下,似乎不符合正统神学的逻辑。似乎可以推论,没什么事情能够确保上帝绝不会做出不符合人类道德观的行为。如果人类的道德观来源于万能的造物主,我们有理由相信多数情况下上帝的行为和人类的道德观是一致的,但假使祂偏要做出不符合人类道德观的行为,我们既无可奈何,也没资格批判。上帝就是爱,造你是爱你,助你是爱你,杀你也是爱你,这样的“至善”,在非信徒的眼中是很难给予赞美的。 永恒性(Eternity)亚里斯多德提出了质料因和形式因(动力因和目的因没有那么基本),任何事物都是质料和形式的结合,质料是被动的潜能,形式是能动的现实。 在亚里斯多德哲学的影响下,基督教神学中所谓的“存在大链条”(Great chain of Being)言明,上帝是不含任何潜能的纯现实,位于最高等级;下面是四个等级的天使序列,然后是人,最高的肉体化存在及最低的精神化存在。再下面是三个等级的生命形式,最底层是物质,不含任何实现性的纯潜能。 由此我们可以分析一下上帝永恒的内涵: 首先是单一性,祂不是质料和形式的合一,而是纯形式;其次是独存性,不依赖于任何别的事物而存在;最后是不变性,会变化的必不永恒,永恒的必不变化,变化和时间内在相关,因此上帝必在时间之外。 按照一般的哲学理解,一切事物(概念)的规定性在与该物(概念)与他物(概念)的相互关系当中。基督教神学同样沿袭的是这种思路,例如上帝之为创世主在于祂和世界的关系之上,这种关系就是,上帝创造了世界。如果本质寓于关系,关系的变化理应带来本质的变化。体现了上帝和世界之间关系的创世活动,本身就是一种带有时间属性和关系属性的变化,它理应带来上帝本质的变化。而按照上帝的永恒性,上帝永不发生任何变化…… 人格性(Personality)上帝在旧约中呈现出人格的多面性,有时候是万能的救主,有时候是可敬的圣者,有时候是专横的暴君;新约中的上帝的人格形象更单纯一点,就是慈父,就是爱的化身。 上帝人格性的系统哲学阐述始于近、现代,权威是生于1878年的德国人马丁•布伯。其关系哲学认为,人对世界有两种基本人生关系,“我—它”、“我—你”。人和上帝的关系是对“我—你”的统摄,上帝不是超然物外的神秘存在、抽象原理、理念等,而是必然介入人类的“永恒的你”,只有通过“我—永恒的你”的途径才能真正认识上帝。 和罗素合写了巨著《数学原理》的英国人怀特海创立了过程哲学,成为革新传统上帝观念的旗帜。这种学说认为,正统神学本质上是一种实体神学,把单一性、不变性、独存性、超时间性等僵化规定充作上帝最重要最完美的属性。过程哲学反对上帝的这些形而上属性,认为世界不是“实体 ”的聚集,而是“过程”的聚集,上帝寓于同人类的交互关系和过程当中。总之,这是一种新的上帝观,以后我们还会碰到。 本体论证明正统神学家认为这是一种理想的证明思路,它不依赖于经验;另一些哲学家认为这类思路属于逻辑诡辩。本体论证明的主要代表人物是十一世纪的安瑟伦,以及十七世纪的笛卡尔。其大致逻辑是,只存在于心灵观念中的上帝并不完美,因此只要上帝是完美的,那就必然既存在于心灵也存在于现实。“不存在”是一种不完美的属性,这个属性不可能附加在完美的上帝身上,因此上帝必然存在。“存在”是上帝内禀的性质,就像“有三个角”是三角形的内禀性质一样。 笛卡尔同时代的法国人迦桑迪反驳说,任何事物首先要存在,其次才能谈论它的属性,不存在的事物既没有完满性,也没有不完满性。加在上帝概念上的完满性并非是什么必然存在的东西,而是我们从不完满世界的诸多理想中抽象出来的。 康德认为,假如删除宾词(存在)保留主词(上帝)会导致矛盾,那么宾词(存在)必然属于主词(上帝);然而如果把主词、宾词全部删除,则没有任何矛盾(让“ 上帝”以及“上帝存在”的说法整个地见鬼去吧)。更釜底抽薪地说,“存在”不是一个实在的宾词,它不能给被定义的概念增添什么实在的属性。例如正二十面体,它也许存在,也许不存在,无论如何,这一概念的诸属性(三维、二十个面、每个面全等)已经被全部定义好了。用数学证明出存在正十面体,这个新增加的“ 存在”并不是“正二十面体”实在属性的一部分。“存在”不过是一个判断中的系动词罢了,所有的存在命题都不是分析判断,而是综合判断。 二十世纪美国分析哲学家马尔科姆站在安瑟伦一边,他的思路是,很多事物都存在,但无所谓完满,可见“存在”本身不是完满性的体现,自然也不是上帝的内禀属性;但,必然存在的事物高于偶在的事物,“必然存在”是完满性的体现,可以作为上帝的内禀属性,这才是安瑟伦的真意。康德的反驳只能对抗普通的“存在命题 ”,不能对抗“必然存在命题”,这两类命题是有区别的。“上帝存在”这个判断暗含了在“存在”与“不存在”这两种可能性中选择了前者,然而“上帝必然存在 ”则根绝了“不存在”这种可能性。“上帝是必然存在的”,和“上帝是全能的、全知的……”之类的命题,具有同样的先验地位。 和安瑟伦同时代的法国僧侣高尼罗曾经用安瑟伦的思路来证明某种想象中无以伦比的海岛仙山必然存在。安瑟伦的回答是,上帝是一个自存、无限的概念,不能简单类比于什么海岛仙山。然而,和马尔科姆同时代的马乔里•黑特比高尼罗更狡猾,他用安瑟伦的思路来证明魔鬼的存在。 宇宙论论证托马斯•阿奎那指明了五条大道:从事物的运动推论到第一推动者;从因果关系的无穷序列推论出第一因;从偶然的存在物推论出必然的存在者;从万事万物的等级推论出完善性;从世界的目的性推论出智慧的存在者。第二、三条被看作经典的宇宙论论证。 第二条,因果序列的论证思路,其前提是世界是可用因果关系来理解的并且由上帝这个第一因给出了最终的理解,显然不可知论者是不会答应的。其次,休谟对传统的因果关系早已作出了批判,认为因果关系不过是对不同时间发生的一些事情作一种习惯性的联想罢了,是人类思维整理的产物。一些当代哲学家根据物理学的发展,认为在微观领域应该放弃传统因果模式,代之以概率,也有主张用系统性、整体性观念代替线性因果关系的。詹姆士用实用主义来消解因果关系,认为事物的根据最终归因于人自身的意愿与需要。最后,即使必定有第一因,凭什么它就是上帝呢? 第三条,大意是一切现实事物都是偶在的,必然有某个必然的存在物作为其产生的根据或者存在的起点。这同第二种思路异曲同工,因此也将遭遇类似的反驳。 托马斯之外,还存在凯拉姆式(即伊斯兰教经院哲学)的宇宙论论证,其大意是万物有其时间上的开端,宇宙也有其时间上的开端,而那就是上帝。这一思路的当代支持者会拿宇宙大爆炸学说来当证据。然而科学理论并不会关注“奇点的存在根据”这样的哲学问题,“奇点之前、之外”在科学上都是无意义的说法,“宇宙的存在需要一个原因即上帝,上帝的存在不需要原因”同“宇宙的存在不需要原因”相比,根据奥卡姆剃刀原则,采信后者。 目的论论证演绎式的目的论论证的典型即前述托马斯的第五条,其哲学渊源来自亚里斯多德。万事有其目的(据说水的目的就是低处),如箭有其目的并需要一位射箭者一般,万事万物需要一位上帝作指挥。这一思路论证力很弱,更值得关注的是归纳式的目的论论证,又称作设计论论证,盛于十八世纪。 设计论论证的代表人物是英国哲学家威廉•佩利。大意是,在海边杂乱的鹅卵石当中捡到一块精密的钟表,即使是不知道钟表原理的人甚至干脆就是没见过钟表的人,也不会认为这是自然产物,可以看出这个小怪物理应是按照某种目的而精心设计出来的被造物,因此必然存在一个智慧的设计者。同理,宇宙世界比钟表要精妙得多,因此必然存在一个有目的、有智慧的设计者,即上帝。 休谟跳出来了。首先,设计论论证是一种经验论证的思路,而在怀疑论的休谟看来,人类不可能获得有关上帝的经验。其次,钟表和宇宙之间的类比链条很脆弱,有什么根据可以阻止我们把宇宙类比为某种巨大而珍奇的植物或者动物呢?类比论证从本性上说就不是必然性的推理,也不可能有什么必然性的结论。再次,喜欢类比,不妨就滥用它吧。例如,钟表以及其他精密的被造物往往是人群合作的结果,即使存在宇宙的设计者,凭什么就是单一、万能的上帝呢?也许是很多神仙?或者甚至是魔鬼?精密的人类创造物凝聚了人类智慧的发展,最初的人造品都是粗糙的,或者我们可以联想到宇宙开初的混沌来自于一个笨拙的上帝而不是什么全知全能的伟大上帝。如果钟表可以随意的类比为宇宙,干脆把人和上帝类比一下算了,上帝也有七情六欲吃喝拉撒何如?另外,设计论论证中,说钟表精密是对比自然(杂乱的海滩鹅卵石)而言的,后面又反过来说宇宙、自然比钟表要精密的多——自然到底是杂乱的还是有秩序(精密)的,取决于设计论者们的随心所欲罢了。 道德论论证这类思路多以不系统的形式散见于个别论著,没有形成大量研究资料或公认代表人物,也不是一种独立而成熟的证明方式,更多的是一种旁证。 常见的形式是,从道德律的客观性出发证明存在神圣的道德立法者,即上帝。或者根据人类所追求的道德理想的完善性崇高性,来证明作为道德主宰的上帝的存在。再者是根据人类基本道德理念的一致性,来证明道德的共同源泉,即上帝。然而所谓道德的客观性、一致性,真正坚信的人并不多,而且道德问题往往是笼统的而不是刚性的,是应然的而不是实然的,难有什么强有力的论证,因此这里不再赘述。 意志论论证最著名的是数学天才帕斯卡的打赌论。他反对人类可能通过理性认识上帝的本质,上帝的存在无法获得彻底的证明,因此信还是不信,就像一场赌博。如果上帝不存在而信,没有太多损失,如果上帝存在而不信,却要下地狱,还是信吧。这个赌局,你的抵押是有限的,但可能的收益——永恒的生命和福祉——是无限的,不试图去搏一搏那无限的收益,是欠缺理智的。 十九世纪末著名的实用主义者威廉•詹姆斯认为,选择可以是有生命力的或者僵死的,有强制性的或者可回避的,价值重大的或无足轻重的。是否信仰上帝,就是有生命力、有强制性、价值重大的选择。理性的有限不足以使我们在这个选择面前产生倾向,中止选择、等待证据几乎类同于选择不信。因此我们必须依赖于情感和意志做出选择。冒丧失真理的危险,还是冒走入谬误的危险?还是选择后者吧,去相信上帝。 批判者认为,显而易见,实用主义式的功利心无法导向神圣的、超越世俗的信仰,而真正的信仰必须是神圣而超越的。权衡利弊绝不是信仰的正道。这在道德领域也是类似的,靠权衡、计算来选择遵守某些道德规则的人,不会被看做真正高尚的人。伦理的本性不是规则主义的而是美学主义的,高尚的人以德为美而不是以德为利。同样,真正的信仰者以信仰为真理,而不是搞投机。 上帝的否证——罪恶的存在罪恶分为两种,自然的灾难,和人类的邪恶。如果上帝是至善的,就不应该有这些罪恶,如果上帝是全知的,就必能察觉所有罪恶,如果上帝是全能的,就有能力让所有罪恶消失。但,罪恶是存在的,因此不存在同时满足至善、全能、全知这三个属性的上帝。 针对这条否证的思路,出现了以下种种回应,往往被称作神正论。 罪恶虚幻说诞生于十九世纪末的基督教科学派的观点,认为唯有善、健康是真实的,疾病、痛苦、罪恶都是幻觉。这一极端教派同时遭到宗教界和非宗教界的反对。 奥古斯丁神正论恶是善之匮乏、人之堕落。疾病和伤害,就是健康的匮乏;恢复健康,并不是说疾病和伤害跑到别的什么地方去了,当它们不存在于健康的身心之中时,也不存在于任何别处。邪恶也就是善的匮乏,用现代的语言来比喻,上帝创造了热运动(测量读数就是温度值),温度低并不代表上帝创造了什么“热不运动”,而仅仅是热运动的匮乏。善也是这样的,上帝创造的世界只有单纯的善,恶不过是某些地方匮乏善的表现罢了。也就是说恶没有自存性,而是从善那里获得规定性。 把恶叫做善的匮乏,很像是一种语言游戏。杀人放火、拐骗偷盗,这些行为是真实存在的,不管我们叫它什么名字。上帝没有阻止这些东西的出现,似乎总归是要负点责任的。 于是奥古斯丁要说了,最初的恶来自于亚当夏娃的堕落,疾病、灾祸是上帝对堕落之人的惩罚。 如果自然的恶是上帝对人的惩罚,那么无人岛上的火山,是打算惩罚谁呢?人诞生以前的地震、水灾,是打算惩罚谁呢?也许那些自然现象依然有,但既然没有人,就无所谓灾了。可动物呢?难道动物也有罪,需要上帝创造这些自然现象来惩罚它们?大概奥古斯丁不需要为这类问题费神,那个时代地球年龄、物种历史和今天的科学观念完全是两回事。 人类道德的恶引出了著名的自由意志问题。人有自由意志,因此要对自己的罪负责,不能怪上帝,这是奥古斯丁的观点。然而,上帝为什么不创造出永不堕落犯罪的人呢?毕竟,自由意志和永不堕落犯罪是可以相容的,即使失去了堕落犯罪的自由,至少还有做小善还是做大善、用什么方式行善等诸多自由。 伊里奈乌神正论比奥古斯丁更早的哲学家,其观点是,罪恶是人类走向道德成熟、精神完善这一漫漫旅程中的必经阶段。在他看来,上帝造人不是一个瞬间活动,而是一个发展的过程;其次,人类也不是奥古斯丁理解的那样首先是完善的然后才堕落了,人类原初的无罪、清白并非是道德意义上的成熟或完善。最后,罪恶不是堕落,而是必由之路。 该思想的当代阐释者约翰•希克认为,现成赋予人类的、不需要人类抉择取舍的善,比不上在困难和考验之下、通过自由意志的选择和取舍最终获得的善。上帝故意将人置于一种不完善的状态,通过自由意志和道德奋斗,最终实现彻底完善的人性。对于偏离道德的人,上帝也将一视同仁给予爱心和信任,继续在来生为他们提供机会,直到把他们引入天国。 显然,这一派的神正论明显偏离了传统的造人、原罪、地狱惩罚说,遭到保守基督教学者的否定。 全能的上帝观也要出面反对了,既然全能,为什么不能创造一个既使道德进步成为可能、又没有如许多的罪恶的世界呢?手段和目的何以冲突至如此地步?也许白日梦般的极乐世界不值得珍爱,但如此多罪恶的世界是不是过分了? 过程神学神正论戴维•格里芬1976年出版《上帝、力量与罪恶:过程的神正论》标志着过程神学的形成。其历史渊源是过程哲学。该哲学站在有机动态的出发点理解存在,反对过去笛卡尔式的主体、客体二元论,主张“一个现实实有(事件)是如何生成的”也就构成了“那现实实有是什么”,存在就是由生成构成的,谓之过程原理。 以格里芬为代表的过程神学离开了正统的基督教,它反对了上帝的全能性,描绘了某种古怪的过程化了的上帝,祂不是主宰而是吸引或劝导着创造活动,祂承担创造的风险,分担人类的各种情绪。善恶问题同和谐、强度之类的概念挂钩。其所谓善,更多的是审美意义上容纳了负面的某种善,而不是传统道德上只有正面的善。过程神学中的上帝似乎不是圣经中的上帝,也不是罪人的上帝,而是圣人、天才们的上帝。 宗教经验在围绕基督教的无数争论中,几乎都是理性、逻辑、思维的较量,不相干的旁观者几乎要产生一种错觉——这些复杂争论的思路、内涵乃是把握基督教信仰的关键。而实质上,在真正的信徒眼中,宗教经验高于宗教教义,因为这是对信仰对象的直接见证或者内在直观,而教义是间接论证或外在表达,故有宗教经验是宗教信仰之内核的说法。宗教经验带来的神奇感、神秘感、化一感、无时间感、愉悦或者安宁感、无法言传感,带来的对上帝的依赖感、敬畏感、向往感,是信徒真正的信仰源泉,而神学论证、哲学沉思,不过是内在信仰的派生物罢了。关于宗教经验的理解,有情感论、感觉论、超验论等,无论如何,它是内在的以及个体化的,难以用理性滔滔不绝地分析批判,只好打住。 迈蒙尼德的否定描述理论摩西•本•迈蒙尼德是中世纪犹太教哲学家,对宗教语言做了深入讨论,强调要从否定的角度入手描述上帝。譬如说人是二足动物,是一种肯定性的描述,刻画了人的某个本质属性;说人不是三足动物,也给予人以某种程度的规定性,但并未触及人的本质属性。前一种描述蕴含了后一种,反之则不然。用一系列肯定性的属性来描述上帝,会误导我们以为上帝是诸属性的合成物,并自以为了解到有关上帝本质的知识。而上帝其实是一种超越的、单一的存在,祂不包含任何构成性的因素、属性,其本质也在人类理解之外。因此,我们应该用否定的方式来描述上帝,所谓否定不是指词语本身的表述形式,而是对用来描述上帝的那些宾词必须从否定的意义上来理解。例如说上帝是存在着的,应理解为上帝是不可能不存在的;说上帝永恒的,应理解为上帝的存在是无因的,等等。 逻辑实证主义的意义标准一个命题有无意义,取决于其“可证实性”。如果发现了外星人,那么“外星人存在”被证实为真,如果宇宙是有限的并且彻底搜索过之后没有发现外星人,那么“外星人存在”被证实为假。尽管我们目前既没有发现外星人也没有能力彻底搜索宇宙,但“外星人存在”是有意义的命题,因为它具有“可证实性”,我们知道在什么条件下它可以被证实为真或者证实为假。相反,“物质是第一性”或者“精神是第一性”,“柏拉图的理念世界是存在的”,“存在的本质是虚无”……这类命题统统没有可证实性,是形而上学,在逻辑实证主义眼中毫无意义。这套意义标准在二十世纪五六十年代的宗教领域引出了一场寓言论战。 顺便说一句,假设把证实主义或者证伪主义的原则概括为一句话,那么这句话本身能否得到证实或者证伪?这个意义标准似乎把自己也列入了无意义的行列当中…… 无论如何,逻辑实证主义乃至于证伪主义的意义标准似乎最好限缩在自然科学领域,在人类复杂多样化的语言现象中,还有别的标准值得关注,例如象征主义、功能主义等等。 隐身花匠的寓言约翰•威兹德姆提出了这一经典寓言:两个人来到长期无人照管的花园,惊讶地发现有几棵花木在杂草丛中生机勃勃,甲认为有陌生而好心的花匠一直以来都在照看这些花。询问邻里后发现没人来过,甲认为花匠是趁大伙都睡着之后的夜里来照看花的。乙认为即使是夜里干活久而久之也会被邻居们听见的,再说照看花的人怎么可能做到不踩倒任何杂草呢?甲又举证说花园里能察觉出秩序感、美感,一定有人照看,既然没人发现,他必定是个隐身人。两人继续搜寻各种迹象,有时候一些迹象似乎表明真的有花匠来过,有时候一些迹象又倾向于否认这一点。直到两人充分了解了花园的所有细节并坦诚理智地交换所有看法之后,甲还是相信有花匠,乙还是不信。此时,花匠的假设不再是一个经验可以判定的命题了,失去了可证实性。甲的关于存在隐身花匠的解释,和乙的根本不存在花匠的解释,没有实质区别,只有感受方式的区别。威兹德姆认为,当前无神论有神论的争论也大致如此,双方面对同一个世界,分享了所有经验,交换了所有看法,结果还是一方信另一方不信。双方的区别只是感觉方式上的,用不同的命题或语言来表达不同的感受,但指向的却是同一个世界。双方的命题都不是事实的陈述,也都不可证实。 后来人继续提出了探险家寓言、牛津疯子寓言、游击队员寓言、旅行者寓言等,试图将有神论和无神论之间的争论同构成自己编出来的寓言故事,后出现的寓言总是试图指出前面的寓言在同构方面存在误差,并导致了结论上的误差。类比、比喻、同构似乎从原理上就不可能增加证明力,其作用无非是使问题更加浅白、清晰化,但由于信息的压缩、损失,误差也应该是无法避免的。因此这类讨论进路好像没有太大的价值。 (全文完)
Oracle基本数据类型存储格式浅析Oracle基本数据类型存储格式浅析(一)——字符类型前一阵看完文档,对oracle的基本数据类型的存储格式有了一些了解,最近有做了一些测试进行了验证。打算整理总结一下,这一篇主要说明字符类型的存储格式。主要包括char、varchar2和long等几种类型。 SQL> create table test_char (char_col char(10), varchar_col varchar2(10), long_col long);
表已创建。
SQL> insert into test_char values ('abc', '123', ',fd');
已创建 1 行。
SQL> commit;
提交完成。
SQL> select rowid from test_char;
ROWID
------------------
AAAB3LAAFAAAAAgAAA
根据rowid的定义规则,第7~9位是表示的是数据文件,F表示5,而10~15位表示的是在这个数据文件中的第几个BLOCK,g表示 32。(rowid编码相当于64进制。用A~Z a~z 0~9 + /共64个字符表示。A表示0,B表示1,……,a表示26,……,0表示52,……,+表示62,/表示63。) 我们根据计算的结果去dump这个block。 SQL> ALTER SYSTEM DUMP DATAFILE 5 BLOCK 32; 系统已更改。 打开产生的trace文件: data_block_dump,data header at 0x3421064
===============
tsiz: 0x1f98
hsiz: 0x14
pbl: 0x03421064
bdba: 0x01400020
76543210
flag=--------
ntab=1
nrow=1
frre=-1
fsbo=0x14
fseo=0x1f82
avsp=0x1f6e
tosp=0x1f6e
0xe:pti[0] nrow=1 offs=0
0x12:pri[0] offs=0x1f82
block_row_dump:
tab 0, row 0, @0x1f82
tl: 22 fb: --H-FL-- lb: 0x1 cc: 3
col 0: [10] 61 62 63 20 20 20 20 20 20 20
col 1: [ 3] 31 32 33
col 2: [ 3] 2c 66 64
end_of_block_dump
End dump data blocks tsn: 5 file#: 5 minblk 32 maxblk 32
观察dump出来的结果,可以发现以下几点:
SQL> SELECT DUMP(CHAR_COL, 16) D_CHAR FROM TEST_CHAR;
D_CHAR
-------------------------------------------------------------
Typ=96 Len=10: 61,62,63,20,20,20,20,20,20,20
SQL> SELECT DUMP(VARCHAR_COL, 16) D_VARCHAR2 FROM TEST_CHAR;
D_VARCHAR2
-------------------------------------------------------------
Typ=1 Len=3: 31,32,33
SQL> SELECT DUMP(LONG_COL, 16) D_VARCHAR2 FROM TEST_CHAR;
SELECT DUMP(LONG_COL, 16) D_VARCHAR2 FROM TEST_CHAR
*
ERROR 位于第 1 行:
ORA-00997: 非法使用 LONG 数据类型
由于DUMP不支持LONG类型,因此我们使用了alter system dump block的方式,通过比较两种方式得到的结果,发现DUMP()函数不但方便,结果清晰,而且指出了进行DUMP的数据类型,在以后的例子中,除非必要的情况,否则都会采用DUMP()函数的方式进行说明。 下面看一下插入中文的情况,首先看一下数据库的字符集 SQL> select name, value$ from sys.props$ where name like '%CHARACTERSET%';
NAME VALUE$
------------------------------ ------------------------------
NLS_CHARACTERSET ZHS16GBK
NLS_NCHAR_CHARACTERSET AL16UTF16
SQL> insert into test_char values ('定长', '变长', null);
已创建 1 行。
SQL> SELECT DUMP(CHAR_COL, 16) D_CHAR FROM TEST_CHAR;
D_CHAR
----------------------------------------------------------------
Typ=96 Len=10: 61,62,63,20,20,20,20,20,20,20
Typ=96 Len=10: b6,a8,b3,a4,20,20,20,20,20,20
SQL> SELECT DUMP(VARCHAR_COL, 16) D_VARCHAR2 FROM TEST_CHAR;
D_VARCHAR2
----------------------------------------------------------------
Typ=1 Len=3: 31,32,33
Typ=1 Len=4: b1,e4,b3,a4
根据dump结果,可以清楚的看出,普通英文字符和标点用一个字节表示,而中文字符或中文标点需要两个字节来表示。 下面,对比一下nchar和nvarchar2与char、varchar2类型有什么不同。 SQL> create table test_nchar (nchar_col nchar(10), nvarchar_col nvarchar2(10));
表已创建。
SQL> insert into test_nchar values ('nchar定长', 'nvarchar变长');
已创建 1 行。
从这里已经可以看出一些不同了,如果按照刚才中文的计算方法,'nvarchar变长'的长度是8+2*2=12已经超过了数据类型定义的大小,可是为什么插入成功了? 还是dump一下看看结果吧。 SQL> select dump(nchar_col, 16) from test_nchar; DUMP(NCHAR_COL,16) -------------------------------------------------------------- Typ=96 Len=20: 0,6e,0,63,0,68,0,61,0,72,5b,9a,95,7f,0,20,0,20,0,20 SQL> select dump(nvarchar_col, 16) from test_nchar; DUMP(NVARCHAR_COL,16) -------------------------------------------------------------- Typ=1 Len=20: 0,6e,0,76,0,61,0,72,0,63,0,68,0,61,0,72,53,d8,95,7f 这下就明白了,虽然仍然是采用ascii码存储,但是nchar使用的AL16UTF16字符集,编码长度变为2个字节。这样中文使用两个字节,对于可以用一个字节就表示的英文字符,采用了高位补0的方式凑足2位,这样,对于采用AL16UTF16字符集的nchar类型,无论中文还是英文都用2位字符表示。因此'nvarchar变长'的长度是10,并没有超过数据类型的限制。 Oracle基本数据类型存储格式浅析(二)——数字类型这篇文章主要描述NUMBER类型的数据和如何在数据库中存储的。 Oracle的NUMBER类型最多由三个部分构成,这三个部分分别是最高位表示位、数据部分、符号位。其中负数包含符号位,正数不会包括符号位。另外,数值0比较特殊,它只包含一个数值最高位表示位80,没有数据部分。 正数的最高位表示位大于80,负数的最高位表示位小于80。其中一个正数的最高位是个位的话,则最高位表示位为C1,百位、万位依次为C2、C3,百分位、万分为依次为C0、BF。一个负数的最高位为个位的话,最高位表示位为3E,百位、万位依次为3D、3C,百分位、万分位依次为3F、40。 数据部分每一位都表示2位数。这个两位数可能是从0到99,如果是数据本身是正数,则分别用二进制的1到64表示,如果数据本身是负数,则使用二进制65到2表示。 符号位用66表示。 上面的这些是我通过DUMP结果总结出来的,对于上面提到的这些关系常数,Oracle之所以这样选择是有道理的,我们后面根据例子也可以推导出来,而且会进一步说明为什么会采用这种方式表示。这里列出的意思是使大家先对NUMBER类型数据有一个大概的了解。 下面我们通过一个例子详细说明: SQL> CREATE TABLE TEST_NUMBER (NUMBER_COL NUMBER);
表已创建。
SQL> INSERT INTO TEST_NUMBER VALUES (0);
已创建 1 行。
SQL> INSERT INTO TEST_NUMBER VALUES (1);
已创建 1 行。
SQL> INSERT INTO TEST_NUMBER VALUES (2);
已创建 1 行。
SQL> INSERT INTO TEST_NUMBER VALUES (25);
已创建 1 行。
SQL> INSERT INTO TEST_NUMBER VALUES (123);
已创建 1 行。
SQL> INSERT INTO TEST_NUMBER VALUES (4100);
已创建 1 行。
SQL> INSERT INTO TEST_NUMBER VALUES (132004078);
已创建 1 行。
SQL> INSERT INTO TEST_NUMBER VALUES (2.01);
已创建 1 行。
SQL> INSERT INTO TEST_NUMBER VALUES (0.3);
已创建 1 行。
SQL> INSERT INTO TEST_NUMBER VALUES (0.00000125);
已创建 1 行。
SQL> INSERT INTO TEST_NUMBER VALUES (115.200003);
已创建 1 行。
SQL> INSERT INTO TEST_NUMBER VALUES (-1);
已创建 1 行。
SQL> INSERT INTO TEST_NUMBER VALUES (-5);
已创建 1 行。
SQL> INSERT INTO TEST_NUMBER VALUES (-20032);
已创建 1 行。
SQL> INSERT INTO TEST_NUMBER VALUES (-234.432);
已创建 1 行。
SQL> COMMIT;
提交完成。
SQL> COL D_NUMBER FORMAT A50
SQL> SELECT NUMBER_COL, DUMP(NUMBER_COL, 16) D_NUMBER FROM TEST_NUMBER;
NUMBER_COL D_NUMBER
---------- --------------------------------------------------
0 Typ=2 Len=1: 80
1 Typ=2 Len=2: c1,2
2 Typ=2 Len=2: c1,3
25 Typ=2 Len=2: c1,1a
123 Typ=2 Len=3: c2,2,18
4100 Typ=2 Len=2: c2,2a
132004078 Typ=2 Len=6: c5,2,21,1,29,4f
2.01 Typ=2 Len=3: c1,3,2
.3 Typ=2 Len=2: c0,1f
.00000125 Typ=2 Len=3: be,2,1a
115.200003 Typ=2 Len=6: c2,2,10,15,1,4
-1 Typ=2 Len=3: 3e,64,66
-5 Typ=2 Len=3: 3e,60,66
-20032 Typ=2 Len=5: 3c,63,65,45,66
-234.432 Typ=2 Len=6: 3d,63,43,3a,51,66
已选择15行。
下面根据例子得到的结果,对每行进行说明。首先说明两点基本的。DUMP函数返回的TYPE=2表示DUMP的数据类型是NUMBER,LENGTH=N表示数值在数据库中存储的长度是N。
根据Oracle的存储特性,还可以推出Oracle的number类型的取值范围。 Oracle的concept上是这样描述的: The following numbers can be stored in a NUMBER column: Positive numbers in the range 1 x 10^-130 to 9.99...9 x 10^125 with up to 38 significant digits. Negative numbers from -1 x 10^-130 to 9.99...99 x 10^125 with up to 38 significant digits. Zero. 下面来推导出取值范围。 来看符号位,0xC1表示个位。 SQL> select to_number('ff', 'xxx') - to_number('c1', 'xxx') from dual;
TO_NUMBER('FF','XXX')-TO_NUMBER('C1','XXX')
-------------------------------------------
62
由于Oracle是两位、两位存储的,因此最高位相当于62×2=124,而且最高位上最大值是99,因此正数的最大值为9.999……×10^125。 SQL> select to_number('c1', 'xxx') - to_number('80', 'xxx') from dual;
TO_NUMBER('C1','XXX')-TO_NUMBER('80','XXX')
-------------------------------------------
65
最高位相当于65×2=130,因此正数的最小值为1×10^-130。 负数和正数在各使用了一半的编码,因此具有相同的极值范围。 Oracle基本数据类型存储格式浅析(三)——日期类型(一)这篇文章描述DATE类型的数据在Oracle中是以何种格式存放的。 下面通过一个例子进行说明。 SQL> create table test_date (date_col date);
表已创建。
SQL> insert into test_date values (to_date('2000-1-1 0:0:0', 'yyyy-mm-dd hh24:mi:ss'));
已创建 1 行。
SQL> insert into test_date values (to_date('1-1-1 0:0:0', 'yyyy-mm-dd hh24:mi:ss'));
已创建 1 行。
SQL> insert into test_date values (to_date('-1-1-1 0:0:0', 'syyyy-mm-dd hh24:mi:ss'));
已创建 1 行。
SQL> insert into test_date values (to_date('-101-1-1 0:0:0', 'syyyy-mm-dd hh24:mi:ss'));
已创建 1 行。
SQL> insert into test_date values (to_date('-4712-1-1 0:0:0', 'syyyy-mm-dd hh24:mi:ss'));
已创建 1 行。
SQL> insert into test_date values (to_date('9999-12-31 23:59:59', 'syyyy-mm-dd hh24:mi:ss'));
已创建 1 行。
SQL> insert into test_date values (sysdate);
已创建 1 行。
SQL> insert into test_date values (to_date('-4713-1-1 0:0:0', 'syyyy-mm-dd hh24:mi:ss'));
insert into test_date values (to_date('-4713-1-1 0:0:0', 'syyyy-mm-dd hh24:mi:ss'))
*
ERROR 位于第 1 行:
ORA-01841: (全)年度值必须介于 -4713 和 +9999 之间,且不为 0
SQL> insert into test_date values (to_date('0000-1-1 0:0:0', 'yyyy-mm-dd hh24:mi:ss'));
insert into test_date values (to_date('0000-1-1 0:0:0', 'yyyy-mm-dd hh24:mi:ss'))
*
ERROR 位于第 1 行:
ORA-01841: (全)年度值必须介于 -4713 和 +9999 之间,且不为 0
SQL> col dump_date format a80
SQL> select to_char(date_col, 'syyyy-mm-dd hh24:mi:ss'), dump(date_col) dump_date from test_date;
TO_CHAR(DATE_COL,'SY DUMP_DATE
-------------------- ---------------------------------------
2000-01-01 00:00:00 Typ=12 Len=7: 120,100,1,1,1,1,1
0001-01-01 00:00:00 Typ=12 Len=7: 100,101,1,1,1,1,1
-0001-01-01 00:00:00 Typ=12 Len=7: 100,99,1,1,1,1,1
-0101-01-01 00:00:00 Typ=12 Len=7: 99,99,1,1,1,1,1
-4712-01-01 00:00:00 Typ=12 Len=7: 53,88,1,1,1,1,1
9999-12-31 23:59:59 Typ=12 Len=7: 199,199,12,31,24,60,60
2004-12-15 13:56:19 Typ=12 Len=7: 120,104,12,15,14,57,20
已选择7行。
通过最后两条语句已经可以看出Oracle的DATE类型的取值范围是公元前4712年1月1日至公元9999年12月31日。而且根据日期的特定,要不然是公元1年,要不然是公元前1年,不会出现0年的情况。 日期类型长度是7,7个字节分别表示世纪、年、月、日、时、分和秒。 由于不会出现0的情况,月和日都是按照原值存储的,月的范围是1~12,日的范围是1~31。 由于时、分、秒都会出现0的情况,因此存储时采用原值加1的方式。0时保存为1,13时保存为14,23时保存为24。分和秒的情况与小时类似。小时的范围是0~23,在数据库中以1~24保存。分和秒的范围都是0~59,在数据库中以1~60保存。 年和世纪的情况相对比较复杂,可分为公元前和公元后两种情况。由于最小的世纪的值是-47(公元前4712年),最大值是99(公元9999年)。为了避免负数的产生,oracle把世纪加100保存在数据库中。公元2000年,世纪保存为120,公元9999年,世纪保存为199,公元前101年,世纪保存为99(100+(-1)),公元前4712年,世纪保存为53(100+(-47))。 注意,对于公元前1年,虽然已经是公元前了,但是表示世纪的前两位的值仍然是0,因此,这时的保存的世纪的值仍然是100。世纪的范围是-47~99,保存的值是53~199。 年的保存与世纪的保存方式类似,也把年的值加上100进行保存。对于公元2000年,年保持为100,公元1年保存为101,公元2004年保存为 104,公元9999年保存为199,公元前1年,保存为99(100+(-1)),公元前101年,保存为99(100+(-1)),公元前4712年保存为88(100+(-12))。对于公元前的年,保存的值总是小于等于100,对于公元后的年,保存的值总是大于等于100。年的范围是0~99,保存的值是1~199。 注意:一般的世纪,都包含了100年,而对于0世纪,由于包含公元前和公元后两部分且不包含0年,因此包含了198年。 Oracle基本数据类型存储格式浅析(三)——日期类型(二)这篇文章描述TIMESTAMP类型的数据在Oracle中是以何种格式存放的。 下面通过一个例子进行说明。 SQL> create table test_time (col_time timestamp);
表已创建。
SQL> insert into test_time values (to_timestamp('0001-1-1 0:0:0.0', 'syyyy-mm-dd hh24:mi:ss.ff'));
已创建 1 行。
SQL> insert into test_time values (to_timestamp('2000-1-1 0:0:0.0', 'syyyy-mm-dd hh24:mi:ss.ff'));
已创建 1 行。
SQL> insert into test_time values (to_timestamp('9999-12-31 23:59:59.999999', 'syyyy-mm-dd hh24:mi:ss.ff'));
已创建 1 行。
SQL> insert into test_time values (to_timestamp('-0001-1-1 0:0:0.0', 'syyyy-mm-dd hh24:mi:ss.ff'));
已创建 1 行。
SQL> insert into test_time values (to_timestamp('-0100-3-4 13:2:3.234015', 'syyyy-mm-dd hh24:mi:ss.ff'));
已创建 1 行。
SQL> insert into test_time values (systimestamp);
已创建 1 行。
SQL> insert into test_time values (to_timestamp('2000-1-1 0:0:0.123456789', 'syyyy-mm-dd hh24:mi:ss.ff9'));
已创建 1 行。
SQL> commit;
提交完成。
SQL> select to_char(col_time, 'syyyy-mm-dd hh24:mi:ss.ff9') time, dump(col_time) dump_time
2 from test_time;
TIME DUMP_TIME
------------------------------ ----------------------------------------------------
0001-01-01 00:00:00.000000000 Typ=180 Len=7: 100,101,1,1,1,1,1
2000-01-01 00:00:00.000000000 Typ=180 Len=7: 120,100,1,1,1,1,1
9999-12-31 23:59:59.999999000 Typ=180 Len=11: 199,199,12,31,24,60,60,59,154,198,24
-0001-01-01 00:00:00.000000000 Typ=180 Len=7: 100,99,1,1,1,1,1
-0100-03-04 13:02:03.234015000 Typ=180 Len=11: 99,100,3,4,14,3,4,13,242,201,24
2004-12-15 16:14:52.738000000 Typ=180 Len=11: 120,104,12,15,17,15,53,43,252,252,128
2000-01-01 00:00:00.123457000 Typ=180 Len=11: 120,100,1,1,1,1,1,7,91,205,232
已选择7行。
与DATE类型对比可以发现,对于TIMESTAMP类型,如果不包含微秒信息或者微秒值为0,那么存储结果和DATE完全相同。当微秒值为0时,Oracle为了节省空间,不会保存微秒信息。 如果毫秒值不为0,Oracle把微秒值当作一个9位数的数字来保存。 比如999999000,保存为59,154,198,24。234015000保存为13,242,201,24。 SQL> select to_char(999999000, 'xxxxxxxxxx') from dual;
TO_CHAR(999
-----------
3b9ac618
SQL> select to_number('3b', 'xxx') one, to_number('9a', 'xxx') two,
2 to_number('c6', 'xxx') three, to_number('18', 'xxx') four from dual;
ONE TWO THREE FOUR
---------- ---------- ---------- ----------
59 154 198 24
SQL> select to_char(234015000, 'xxxxxxxx') from dual;
TO_CHAR(2
---------
df2c918
SQL> select to_number('d', 'xxx') one, to_number('f2', 'xxx') two,
2 to_number('c9', 'xxx') three, to_number('18', 'xxx') four from dual;
ONE TWO THREE FOUR
---------- ---------- ---------- ----------
13 242 201 24
另外,注意一点,不指定精度的情况下,TIMESTAMP默认取6位。长度超过6位,会四舍五入到6位。如果希望保存9位的TIMESTAMP,必须明确指定精度。 SQL> alter table test_time modify (col_time timestamp(9));
表已更改。
SQL> insert into test_time values (to_timestamp('2000-1-1 0:0:0.123456789', 'syyyy-mm-dd hh24:mi:ss.ff9'));
已创建 1 行。
SQL> select to_char(col_time, 'syyyy-mm-dd hh24:mi:ss.ff9') time, dump(col_time) dump_time
2 from test_time;
TIME DUMP_TIME
------------------------------ ---------------------------------------------------
0001-01-01 00:00:00.000000000 Typ=180 Len=7: 100,101,1,1,1,1,1
2000-01-01 00:00:00.000000000 Typ=180 Len=7: 120,100,1,1,1,1,1
9999-12-31 23:59:59.999999000 Typ=180 Len=11: 199,199,12,31,24,60,60,59,154,198,24
-0001-01-01 00:00:00.000000000 Typ=180 Len=7: 100,99,1,1,1,1,1
-0100-03-04 13:02:03.234015000 Typ=180 Len=11: 99,100,3,4,14,3,4,13,242,201,24
2004-12-15 16:14:52.738000000 Typ=180 Len=11: 120,104,12,15,17,15,53,43,252,252,128
2000-01-01 00:00:00.123457000 Typ=180 Len=11: 120,100,1,1,1,1,1,7,91,205,232
2000-01-01 00:00:00.123456789 Typ=180 Len=11: 120,100,1,1,1,1,1,7,91,205,21
已选择8行。
Oracle基本数据类型存储格式浅析(三)——日期类型(三)如果直接在SQL语句中对SYSDATE或由TO_DATE函数生成日期进行DUMP操作,会发现得到的结果与DUMP数据库中保存的日期的结果不一样。 SQL> truncate table test_date;
表已截掉。
SQL> insert into test_date values (to_date('2004-12-17 16:42:42', 'syyyy-mm-dd hh24:mi:ss'));
已创建 1 行。
SQL> col dump_date format a65
SQL> select to_char(date_col, 'syyyy-mm-dd hh24:mi:ss') dat, dump(date_col) dump_date from test_date;
DAT DUMP_DATE
-------------------- ---------------------------------------------------------
2004-12-17 16:42:42 Typ=12 Len=7: 120,104,12,17,17,43,43
SQL> select to_char(to_date('2004-12-17 16:42:42', 'syyyy-mm-dd hh24:mi:ss'), 'syyyy-mm-dd hh24:mi:ss') dat,
2 dump(to_date('2004-12-17 16:42:42', 'syyyy-mm-dd hh24:mi:ss')) dump_date from dual;
DAT DUMP_DATE
-------------------- ---------------------------------------------------------
2004-12-17 16:42:42 Typ=13 Len=8: 212,7,12,17,16,42,42,0
存储在数据库中的DATE类型是12,而直接在SQL中使用的DATE类型是13。而且二者的长度以及表示方式都不相同。这两种类型的不同指出主要体现在两点:一:时、分、秒的表示不同;二、世纪和年的表示不同。 SQL中使用DATE的时分秒没有采用加1存储方式,而且原值存储。 SQL中使用DATE没有采用世纪、年的方式保持,而是采用了按数值保存的方式。第一位表示低位,第二位表示高位。低位表示最大的值是255。如上面的例子中,212+7×256=2004。 SQL> select to_char(to_date('-2004-12-17 16:42:42', 'syyyy-mm-dd hh24:mi:ss'), 'syyyy-mm-dd hh24:mi:ss') dat,
2 dump(to_date('-2004-12-17 16:42:42', 'syyyy-mm-dd hh24:mi:ss')) dump_date from dual;
DAT DUMP_DATE
-------------------- ---------------------------------------------------
-2004-12-17 16:42:42 Typ=13 Len=8: 44,248,12,17,16,42,42,0
SQL> select dump(to_date('-1-1-1', 'syyyy-mm-dd')) from dual;
DUMP(TO_DATE('-1-1-1','SYYYY-MM-D
---------------------------------
Typ=13 Len=8: 255,255,1,1,0,0,0,0
对于公元前的日期,Oracle从255,255开始保存。公元前的年的保存的值和对应的公元后的年的值相加的和是256,255。如上例中的公元2004年和公元前2004年的值相加:212+44=256,7+248=255。 SQL中DATE类型最后还包括一个0,似乎目前没有使用。 Oracle基本数据类型存储格式浅析(三)——日期类型(四)本文对TIMESTAMP WITH LOCAL TIME ZONE和TIMESTAMP WITH TIME ZONE类型的存储格式进行简单的说明。 SQL> CREATE TABLE TEST_TIMESTAMP(TIME1 TIMESTAMP(9), TIME2 TIMESTAMP(6) WITH LOCAL TIME ZONE, 2 TIME3 TIMESTAMP(4) WITH TIME ZONE); 表已创建。 SQL> INSERT INTO TEST_TIMESTAMP VALUES (SYSTIMESTAMP, SYSTIMESTAMP, SYSTIMESTAMP); 已创建 1 行。 SQL> SELECT * FROM TEST_TIMESTAMP; TIME1 ---------------------------------------------------- TIME2 ---------------------------------------------------- TIME3 ---------------------------------------------------- 11-1月 -05 11.08.15.027000000 下午 11-1月 -05 11.08.15.027000 下午 11-1月 -05 11.08.15.0270 下午 +08:00 SQL> SELECT DUMP(TIME1, 16), DUMP(TIME2, 16), DUMP(TIME3, 16) FROM TEST_TIMESTAMP; DUMP(TIME1,16) ------------------------------------------------------------- DUMP(TIME2,16) ------------------------------------------------------------- DUMP(TIME3,16) ------------------------------------------------------------- Typ=180 Len=11: 78,69,1,b,18,9,10,1,9b,fc,c0 Typ=231 Len=11: 78,69,1,b,18,9,10,1,9b,fc,c0 Typ=181 Len=13: 78,69,1,b,10,9,10,1,9b,fc,c0,1c,3c 可以发现,如果客户端和数据库中的时区是一致的,那么TIMESTAMP和TIMESTAMP WITH LOCAL TIME ZONE存储的数据是完全一样的。 TIMESTAMP WITH TIME ZONE则略有不同,它保存的是0时区的时间,和所处的时区信息。 修改客户端主机的时区,由东8区(+8区)改为0时区。 SQL> INSERT INTO TEST_TIMESTAMP VALUES (SYSTIMESTAMP, SYSTIMESTAMP, SYSTIMESTAMP); 已创建 1 行。 修改客户端主机的时区,改为西5区(-5时区)。 SQL> INSERT INTO TEST_TIMESTAMP VALUES (SYSTIMESTAMP, SYSTIMESTAMP, SYSTIMESTAMP); 已创建 1 行。 修改客户端主机的时区,改为西12区(-12时区)。 SQL> INSERT INTO TEST_TIMESTAMP VALUES (SYSTIMESTAMP, SYSTIMESTAMP, SYSTIMESTAMP); 已创建 1 行。 修改客户端主机的时区,改为东13区(+13时区)。 SQL> INSERT INTO TEST_TIMESTAMP VALUES (SYSTIMESTAMP, SYSTIMESTAMP, SYSTIMESTAMP); 已创建 1 行。 修改客户端主机的时区,改为西3.5区(-3.5时区)。 SQL> INSERT INTO TEST_TIMESTAMP VALUES (SYSTIMESTAMP, SYSTIMESTAMP, SYSTIMESTAMP); 已创建 1 行。 修改客户端主机的时区,改为东9.5区(+9.5时区)。 SQL> INSERT INTO TEST_TIMESTAMP VALUES (SYSTIMESTAMP, SYSTIMESTAMP, SYSTIMESTAMP); 已创建 1 行。 SQL> COMMIT; 提交完成。 修改客户端主机的时区,改回东8区(+8时区)。 SQL> SELECT * FROM TEST_TIMESTAMP;
TIME1
-----------------------------------------------
TIME2
-----------------------------------------------
TIME3
-----------------------------------------------
11-1月 -05 11.08.15.027000000 下午
11-1月 -05 11.08.15.027000 下午
11-1月 -05 11.08.15.0270 下午 +08:00
11-1月 -05 03.11.43.746000000 下午
11-1月 -05 11.11.43.746000 下午
11-1月 -05 03.11.43.7460 下午 +00:00
11-1月 -05 10.14.08.987000000 上午
11-1月 -05 11.14.08.987000 下午
11-1月 -05 10.14.08.9870 上午 -05:00
11-1月 -05 03.15.01.732000000 上午
11-1月 -05 11.15.01.732000 下午
11-1月 -05 03.15.01.7320 上午 -12:00
12-1月 -05 04.20.21.522000000 上午
11-1月 -05 11.20.21.522000 下午
12-1月 -05 04.20.21.5220 上午 +13:00
11-1月 -05 02.15.16.567000000 下午
12-1月 -05 01.45.16.567000 上午
11-1月 -05 02.15.16.5670 下午 -03:30
12-1月 -05 03.16.54.992000000 上午
12-1月 -05 01.46.54.992000 上午
12-1月 -05 03.16.54.9920 上午 +09:30
已选择7行。
SQL> SELECT DUMP(TIME1, 16), DUMP(TIME2, 16), DUMP(TIME3, 16) FROM TEST_TIMESTAMP;
DUMP(TIME1,16)
-------------------------------------------------------------
DUMP(TIME2,16)
-------------------------------------------------------------
DUMP(TIME3,16)
-------------------------------------------------------------
Typ=180 Len=11: 78,69,1,b,18,9,10,1,9b,fc,c0
Typ=231 Len=11: 78,69,1,b,18,9,10,1,9b,fc,c0
Typ=181 Len=13: 78,69,1,b,10,9,10,1,9b,fc,c0,1c,3c
Typ=180 Len=11: 78,69,1,b,10,c,2c,2c,77,e,80
Typ=231 Len=11: 78,69,1,b,18,c,2c,2c,77,e,80
Typ=181 Len=13: 78,69,1,b,10,c,2c,2c,77,e,80,14,3c
Typ=180 Len=11: 78,69,1,b,b,f,9,3a,d4,6c,c0
Typ=231 Len=11: 78,69,1,b,18,f,9,3a,d4,6c,c0
Typ=181 Len=13: 78,69,1,b,10,f,9,3a,d4,6c,c0,f,3c
Typ=180 Len=11: 78,69,1,b,4,10,2,2b,a1,6f,0
Typ=231 Len=11: 78,69,1,b,18,10,2,2b,a1,6f,0
Typ=181 Len=13: 78,69,1,b,10,10,2,2b,a1,6f,0,8,3c
Typ=180 Len=11: 78,69,1,c,5,15,16,1f,1d,16,80
Typ=231 Len=11: 78,69,1,b,18,15,16,1f,1d,16,80
Typ=181 Len=13: 78,69,1,b,10,15,16,1f,1d,16,80,21,3c
Typ=180 Len=11: 78,69,1,b,f,10,11,21,cb,bb,c0
Typ=231 Len=11: 78,69,1,c,2,2e,11,21,cb,bb,c0
Typ=181 Len=13: 78,69,1,b,12,2e,11,21,cb,bb,c0,11,1e
Typ=180 Len=11: 78,69,1,c,4,11,37,3b,20,b8,0
Typ=231 Len=11: 78,69,1,c,2,2f,37,3b,20,b8,0
Typ=181 Len=13: 78,69,1,b,12,2f,37,3b,20,b8,0,1d,5a
SQL> SELECT TO_NUMBER('1C', 'XXX'), TO_NUMBER('3C', 'XXX') FROM DUAL;
TO_NUMBER('1C','XXX') TO_NUMBER('3C','XXX')
--------------------- ---------------------
28 60
SQL> SELECT TO_NUMBER('14', 'XXX'), TO_NUMBER('3C', 'XXX'), TO_NUMBER('143C', 'XXXXXXX') FROM DUAL;
TO_NUMBER('14','XXX') TO_NUMBER('3C','XXX')
--------------------- ---------------------
20 60
SQL> SELECT TO_NUMBER('3C', 'XXX') , TO_NUMBER('1E', 'XXX'), TO_NUMBER('5A', 'XXX') FROM DUAL;
TO_NUMBER('3C','XXX') TO_NUMBER('1E','XXX') TO_NUMBER('5A','XXX')
--------------------- --------------------- -------------------
60 30 90
可以看出,修改时区会导致系统TIMESTAMP时间发生变化,但是对于TIMESTAMP WITH LOCAL TIME ZONE类型,总是将系统的时间转化到数据库服务器上时区的时间进行存储。 TIMESTAMP WITH TIME ZONE保存的是当前时间转化到0时区的对应的时间,并通过最后两位来保存时区信息。 第一位表示时区的小时部分。0时区用0x14表示。东n区在这个基础上加n,西n区在这个基础上减n。我们所处的东8区表示为0x1C。西5区表示为0xF。 第二位表示时区的分钟部分。标准是0x3C,即60分钟。对于东时区的半区,在这个基础上加上30分钟,如果是西时区,则减去30分钟。 Oracle基本数据类型存储格式浅析(四)——ROWID类型(一)Oracle的ROWID用来唯一标识表中的一条记录,是这条数据在数据库中存放的物理地址。 Oracle的ROWID分为两种:物理ROWID和逻辑ROWID。索引组织表使用逻辑ROWID,其他类型的表使用物理ROWID。其中物理 ROWID在Oracle的8版本中进行了扩展,Oracle7及以下版本使用约束ROWID,Oracle8及以上版本使用扩展ROWID。本文描述物理扩展ROWID,由于约束ROWID仅仅是为了兼容早期版本,因此不做讨论。 SQL> create table test_rowid (id number, row_id rowid); 表已创建。 SQL> insert into test_rowid values (1, null); 已创建 1 行。 SQL> update test_rowid set row_id = rowid where id = 1; 已更新 1 行。 SQL> commit; 提交完成。 SQL> select rowid, row_id from test_rowid; ROWID ROW_ID ------------------ ------------------ AAABnRAAGAAAACWAAA AAABnRAAGAAAACWAAA Oracle的物理扩展ROWID有18位,每位采用64位编码,分别用A~Z、a~z、0~9、+、/共64个字符表示。A表示0,B表示1,……Z表示25,a表示26,……z表示51,0表示52,……,9表示61,+表示62,/表示63。 ROWID具体划分可以分为4部分。
上面的例子是AAA,表示第0条记录(总是从0开始计数)。 SQL> alter system dump datafile 6 block 150;
系统已更改。
SQL> select row_id, dump(row_id, 16) dump_rowid from test_rowid;
ROW_ID DUMP_ROWID
------------------ -------------------------------------------------
AAABnRAAGAAAACWAAA Typ=69 Len=10: 0,0,19,d1,1,80,0,96,0,0
找到对应的dump文件,可以发现类型的信息
*** 2004-12-21 17:58:26.000
*** SESSION ID13.91) 2004-12-21 17:58:26.000
Start dump data blocks tsn: 6 file#: 6 minblk 150 maxblk 150
buffer tsn: 6 rdba: 0x01800096 (6/150)
scn: 0x0000.2e389c16 seq: 0x01 flg: 0x06 tail: 0x9c160601
frmt: 0x02 chkval: 0xc97d type: 0x06=trans data
Block header dump: 0x01800096
Object id on Block? Y
seg/obj: 0x19d1 csc: 0x00.2e389c0f itc: 2 flg: O typ: 1 - DATA
fsl: 0 fnx: 0x0 ver: 0x01
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0003.009.00000057 0x0080004b.0042.56 --U- 1 fsc 0x0000.2e389c16
0x02 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000
data_block_dump,data header at 0x651105c
===============
tsiz: 0x3fa0
hsiz: 0x14
pbl: 0x0651105c
bdba: 0x01800096
76543210
flag=--------
ntab=1
nrow=1
frre=-1
fsbo=0x14
fseo=0x3f89
avsp=0x3f7b
tosp=0x3f7b
0xeti[0] nrow=1 offs=0
0x12ri[0] offs=0x3f89
block_row_dump:
tab 0, row 0, @0x3f89
tl: 17 fb: --H-FL-- lb: 0x1 cc: 2
col 0: [ 2] c1 02
col 1: [10] 00 00 19 d1 01 80 00 96 00 00
end_of_block_dump
End dump data blocks tsn: 6 file#: 6 minblk 150 maxblk 150
有时需要查看表的DUMP信息,但是很难准确定位表中数据开始于哪个BLOCK,根据ROWID中包含的信息就可以方便的找到起始BLOCK。 下面简单描述一下ROWID类型是如何存储的。 SQL> select row_id, dump(row_id, 16) dump_rowid from test_rowid; ROW_ID DUMP_ROWID ------------------ ------------------------------------------------- AAABnRAAGAAAACWAAA Typ=69 Len=10: 0,0,19,d1,1,80,0,96,0,0 前4位表示ROWID的前6位,也就是DATA_OBJECT_ID信息。数据以数值的格式保存。 SQL> select to_number('19d1', 'xxxxxx') from dual;
TO_NUMBER('19D1','XXXXXX')
--------------------------
6609
SQL> select 1*64*64 + 39*64 + 17 from dual;
1*64*64+39*64+17
----------------
6609
这里存在一个问题,根据ROWID的取值范围,OBJECT_DATA_ID最大的值是64的6次方,而根据DUMP,oracle只用了4位保存,因此取值范围是256的4次方。 SQL> set numwid 12 SQL> select power(64, 6), power(256, 4), power(64, 6)/power(256, 4) from dual; POWER(64,6) POWER(256,4) POWER(64,6)/POWER(256,4) ------------ ------------ ------------------------ 68719476736 4294967296 16 可见,OBJECT_DATA_ID的最大值是4294967296,当超过这个值时会出现重复的情况。(当然,现实中不大可能)。 后面4位比较特殊,是数据文件号和BLOCK数的“和”值构成。 数据文件的数值乘64后保存在5、6位上。 SQL> select to_number('0180', 'xxxx') from dual;
TO_NUMBER('0180','XXXX')
------------------------
384
SQL> select 6*64 from dual;
6*64
------------
384
同时,6位BLOCK的值,也保存在这4位上,并与数据文件转存结果相加。仍然是以数字格式存放。 SQL> select to_number('96', 'xxx') from dual;
TO_NUMBER('96','XXX')
---------------------
150
SQL> select 2*64 + 22 from dual;
2*64+22
----------
150
由于采用两位保存数据文件的值,且最小单位是64,因此,ROWID中可以保存的数据文件数是1024,超过1024会造成ROWID的重复。 SQL> select 256*256/64 from dual;
256*256/64
----------
1024
由于BLOCK的值和数据文件共用这4位,因此BLOCK的第3位最大值应小于64,这样才能保证ROWID的不重复。因此BLOCK值的最大值应该是4194304。 SQL> select 64*256*256 from dual; 64*256*256 ---------- 4194304 最后两位保存BLOCK中记录的值。这个值的最大值是65536。 SQL> select 256*256 from dual;
256*256
----------
65536
Oracle基本数据类型存储格式浅析(四)——ROWID类型(二) Oracle的文档上没有介绍逻辑ROWID的编码规则,而且通过DUMP的结果也很难反推出编码规则。因此,本文只简单讨论一下逻辑ROWID的存储。 下面来看例子。 SQL> create table test_index (id number primary key, name varchar2(20)) organization index; 表已创建。 SQL> insert into test_index values (1, 'a'); 已创建 1 行。 SQL> commit; 提交完成。 SQL> col dump_rowid format a60 SQL> select rowid, dump(rowid) dump_rowid from test_index; ROWID DUMP_ROWID --------------------------- ---------------------------------------- *BAFAB4wCwQL+ Typ=208 Len=10: 2,4,1,64,7,140,2,193,2,254 逻辑ROWID的DUMP结果前两位都是2和4,最后一位都是254,(我还没有发现其他的情况),由于逻辑ROWID和主键的值有关,所以长度是不定的,因此应该是用来表示开始和结束的。 第3、4位和物理ROWID一样,表示的是相对表空间的数据文件号乘以64的值。 第5、6位表示这条记录在数据文件的第几个BLOCK中。 从第7位开始到DUMP结果的倒数第二位,表示主键的值。首先是主键中第一个字段的长度,这里是2,然后是主键的值,由于是NUMBER类型,因此193,2表示数值1。如果是多个字段组成的主键,第一个字段之后是第二个字段的长度,然后是第二个字段的值……。 SQL> select (1*256 + 64)/64 from dual;
(1*256+64)/64
-------------
5
SQL> select 7*256 + 140 from dual;
7*256+140
----------
1932
SQL> alter system dump datafile 5 block 1932;
系统已更改。
找到相应的dump文件,可以发现刚才插入的记录。 Dump file fracleadmintest4udumptest4_ora_3828.trc
Thu Dec 23 00:17:53 2004
ORACLE V9.2.0.4.0 - Production vsnsta=0
vsnsql=12 vsnxtr=3
Windows 2000 Version 5.1 Service Pack 1, CPU type 586
Oracle9i Enterprise Edition Release 9.2.0.4.0 - Production
With the Partitioning, Oracle Label Security, OLAP and Oracle Data Mining options
JServer Release 9.2.0.4.0 - Production
Windows 2000 Version 5.1 Service Pack 1, CPU type 586
Instance name: test4
Redo thread mounted by this instance: 1
Oracle process number: 9
Windows thread id: 3828, image: ORACLE.EXE
*** 2004-12-23 00:17:53.361
*** SESSION ID8.82) 2004-12-23 00:17:53.301
Start dump data blocks tsn: 5 file#: 5 minblk 1932 maxblk 1932
buffer tsn: 5 rdba: 0x0140078c (5/1932)
scn: 0x0000.00e9f122 seq: 0x01 flg: 0x02 tail: 0xf1220601
frmt: 0x02 chkval: 0x0000 type: 0x06=trans data
Block header dump: 0x0140078c
Object id on Block? Y
seg/obj: 0x1e48 csc: 0x00.e9f113 itc: 2 flg: E typ: 2 - INDEX
brn: 0 bdba: 0x1400789 ver: 0x01
inc: 0 exflg: 0
Itl Xid Uba Flag Lck Scn/Fsc
0x01 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000
0x02 0x0005.008.000000e7 0x00800226.005c.24 --U- 1 fsc 0x0000.00e9f122
Leaf block dump
===============
header address 71963236=0x44a1264
kdxcolev 0
KDXCOLEV Flags = - - -
kdxcolok 0
kdxcoopc 0x90: opcode=0: iot flags=I-- is converted=Y
kdxconco 1
kdxcosdc 0
kdxconro 1
kdxcofbo 38=0x26
kdxcofeo 8026=0x1f5a
kdxcoavs 7988
kdxlespl 0
kdxlende 0
kdxlenxt 0=0x0
kdxleprv 0=0x0
kdxledsz 0
kdxlebksz 8036
row#0[8026] flag: K----, lock: 2
col 0; len 2; (2): c1 02
tl: 5 fb: --H-FL-- lb: 0x0 cc: 1
col 0: [ 1]
Dump of memory from 0x044A31C7 to 0x044A31C8
44A31C0 61010100 [...a]
----- end of leaf block dump -----
End dump data blocks tsn: 5 file#: 5 minblk 1932 maxblk 1932
可以看到,根据DUMP结果的3、4、5、6位可以定位记录的物理位置。 需要注意的是,索引组织表以主键的顺序存储数据,因此插入、更新和删除数据都可能造成一条记录的物理位置发生变化,这时通过ROWID中的 DATAFILE和BLOCK的信息可能就无法正确定位到记录的物理位置。当根据逻辑ROWID访问索引组织表时,首先会根据DATAFILE和 BLOCK信息去找到相应的BLOCK,检查数据是否在这个BLOCK中,如果不在,就通过逻辑ROWID中的主键信息去通过索引扫描,找到这条记录。这就是Oracle文档在提到的physical guess。 下面看一个由字符串和日期组成联合主键的例子。 SQL> create table test_index2 (id char(4), time date,
2 constraint pk_test_index2 primary key (id, time)) organization index;
表已创建。
SQL> insert into test_index2 values ('1', sysdate);
已创建 1 行。
SQL> col dump_rowid format a75
SQL> select rowid, dump(rowid) dump_rowid from test_index2;
ROWID DUMP_ROWID
---------------------------- ------------------------------------------------------------------
*BAFAB5QEMSAgIAd4aAwXASMT/g Typ=208 Len=20: 2,4,1,64,7,148,4,49,32,32,32,7,120,104,12,23,1,35,19,254
可以看出,第7位是字段id的长度4,然后是字符串1和三个空格的ASCII码,这是字符串的存储格式,后面跟着的7是字段time长度,后面七位是日期的存储格式。在逻辑ROWID中,数值、字符和日期类型的存储格式都和它们本身的存储格式一致,这里不在赘述。 一般情况下,使用一位来表示长度,但是如果长度超过了127(16进制DUMP的结果是7F),则长度开始用两位表示。第一位以8开头,这个8只是标识位,表明长度字段现在由两位来表示。例如长度128表示位8080,而支持的最大值3800表示为8ED8。 Oracle基本数据类型存储格式浅析(五)——RAW类型和其他数据类型相比,RAW类型的存储显得直观多了,它和SELECT时数据展示的值完全一样。(SELECT时是按照16进制展示的) SQL> create table test_raw (id number, raw_date raw(10));
表已创建。
SQL> insert into test_raw values (1, hextoraw('ff'));
已创建 1 行。
SQL> drop table test_raw;
表已丢弃。
SQL> create table test_raw (raw_col raw(10));
表已创建。
SQL> insert into test_raw values (hextoraw('ff'));
已创建 1 行。
SQL> insert into test_raw values (hextoraw('0'));
已创建 1 行。
SQL> insert into test_raw values (hextoraw('23fc'));
已创建 1 行。
SQL> insert into test_raw values (hextoraw('fffffffffff'));
已创建 1 行。
SQL> insert into test_raw values (hextoraw('ffffffffffffffffffff'));
已创建 1 行。
SQL> insert into test_raw values (utl_raw.cast_to_raw('051'));
已创建 1 行。
SQL> select raw_col, dump(raw_col, 16) dump_raw from test_raw;
RAW_COL DUMP_RAW
-------------------- -----------------------------------------------
FF Typ=23 Len=1: ff
00 Typ=23 Len=1: 0
23FC Typ=23 Len=2: 23,fc
0FFFFFFFFFFF Typ=23 Len=6: f,ff,ff,ff,ff,ff
FFFFFFFFFFFFFFFFFFFF Typ=23 Len=10: ff,ff,ff,ff,ff,ff,ff,ff,ff,ff
303531 Typ=23 Len=3: 30,35,31
已选择6行。
RAW类型的存储很简单,对比字段的查询结果和DUMP的结果就一目了然了。 需要注意的是,两种转化为RAW的函数之间的差别。当使用HEXTORAW时,会把字符串中数据当作16进制数。而使用UTL_RAW.CAST_TO_RAW时,直接把字符串中每个字符的ASCII码存放到RAW类型的字段中。 SQL> insert into test_raw values ('gg');
insert into test_raw values ('gg')
*
ERROR 位于第 1 行:
ORA-01465: 无效的十六进制数字
SQL> insert into test_raw values (hextoraw('gg'));
insert into test_raw values (hextoraw('gg'))
*
ERROR 位于第 1 行:
ORA-01465: 无效的十六进制数字
SQL> insert into test_raw values (utl_raw.cast_to_raw('gg'));
已创建 1 行。
SQL> select raw_col, dump(raw_col, 16) dump_raw from test_raw;
RAW_COL DUMP_RAW
-------------------- ----------------------------------------------
FF Typ=23 Len=1: ff
00 Typ=23 Len=1: 0
23FC Typ=23 Len=2: 23,fc
6767 Typ=23 Len=2: 67,67
0FFFFFFFFFFF Typ=23 Len=6: f,ff,ff,ff,ff,ff
FFFFFFFFFFFFFFFFFFFF Typ=23 Len=10: ff,ff,ff,ff,ff,ff,ff,ff,ff,ff
303531 Typ=23 Len=3: 30,35,31
已选择7行。
搞出来一个新的来源计数器灭哈哈 用COMET技术实现WEB实时推送应用 Ⅲ最后的补完: 增加了并发同步控制。主要是使用了java.util.concurrent包实现连接池的同步操作。 public class ContinuationPool extends ConcurrentHashMap<Long, TalkContinuation> { } 把连接对象和消息池分离。这样可以单独锁定消息池而不影响更新连接对象。同时每个连接的消息池可以一次接收/容纳/返回多个消息,从而实现连接切换和并发操作中不丢失消息。 public final class TalkContinuation { 通过超时从连接池中清理断开的连接。 public final class TalkContinuationTimeoutException extends Throwable { Service层逻辑实现可以简单的通过调用同步对象的子操作完成。 public boolean sendMessage(MessageBean message) throws Exception { 呈现层只需要修改使之能一次呈现返回的多个消息即可。 onRecvMessage: function(msgs) { 以上,一个简单但是性能较好的系统基本完成了。 用COMET技术实现WEB实时推送应用 Ⅱ首先是基本类型: 消息数据类型: public class MessageBean extends Bean { long fromId, toId; String content; Date timestamp; } 连接池定义: public class ContinuationPool extends HashMap<Long, Continuation> { } IM核心服务类接口: public interface TalkService { public boolean sendMessage(MessageBean message) throws Exception; public MessageBean getMessage(long hostId) throws Exception; } 接口中仅仅包含两个基本的消息通讯服务:sendMessage/getMessage。 最后是核心服务类: sendMessage向负责根据消息的源/目的用户转发消息,getMessage生成一个listen并等待到获取消息返回或者超时。 感想:Live Write的Paste from Visual Studio插件真好用~^_^ 用COMET技术实现WEB实时推送应用 Ⅰ呃。无聊,写个民工技巧。 需求很简单,做一个类似web版gtalk的网页即时通讯系统。要求就是第一实时性要好(消息延时小),第二性能要好(能支持大量用户)——典型的又要马儿跑的好又要马儿不吃草。 查了一下,就只有两条路:轮询或者长连接。轮询是老路,想想就知道性能好不了,而且实时性和性能是直接相关成反比的,也就是相互矛盾的。长连接在NIO出现以后,理论上是可以解决这个问题的。 研究了一下长连接(COMET——我最讨厌JAVA的就是名词太多而且毫无意义-_-)。首先Jetty在这个上面还是有优势的——性能好,开发简单。但是问题是用Jetty的SelectChannelConnector之后我不知道还能不能用Apache做前台(美女?)。因为不知道怎么在AJP上面走NIO。如果不行的话,难道用单机做服务器?寒一下。不过这个没办法。 Jetty的Continuation机制可以很好的和DWR结合,实现方便的request/response流程。很省力。于是先按照IBM的GPS Tracker Sample搭了一个类似的Demo。IBM那篇文章把DWR Reverse AJAX吹得天花乱坠,搭完一看,赤果果的新瓶旧酒。不过是一个Timeout可以很长(因为长连接了)的轮询而已。跑的倒是很好,但是很明显实时性和性能都一般。而且每次response都会给页面加一截script,ugly! 然后用dwr的continuation机制(实际上就是封装了jetty的continuation,只是增加了其他java容器的兼容性,当不是在jetty内运行时直接使用servlet模式,所以后来换成直接用jetty版本的continuation),原理是开一个continuation模式的dwr request作为listener,当其他用户发送消息给这个listener(另一个dwr request)的时候,resume这个suspend的continuation,发送的消息作为response返回给listener,listener呈现消息后再发起下一个continuation listener继续监听。这样的好处是continuation的suspend timeout可以设置的很长,中间有数据(消息)收到可以直接打断suspend以返回消息,然后继续监听。如果没有消息,则除了一个continuation的连接以外不耗费资源。这样性能和实时性都比较好。 启动正常,但是发送数据的时候发现问题:同一个client对同一个jetty发起的continuation是共享的,也就是只有一个。因此有一个continuation在监听时,消息发不出去。必须等到这个continuation超时才能处理下一个dwr request(也就是消息发送request)。出现两个问题:
现在的实现是同一个jetty开两个NIO SelectChannelConnector,一个对应listener,一个对应message sender。这样suspend的listener和接收message request的实际上是两个线程,可以正确的互操作(主要是对同一个continuation的suspend/resume,以及 setObject/getObject)。 经测试,timeout设置不影响任何实时性(设置为10分钟,消息可以实时发送到对方客户端)。而长时间suspending的continuation理论上性能应该很好。 由于跳过了dwr reverse ajax而使用jetty内嵌的continuation机制,dwr仅仅用于传统的request/response,理论上应该没有浏览器兼容问题。实际测试中,IE/FF均能正确的运行测试程序,不同浏览器间消息发送也没有问题。但是IE无法刷新页面(一刷新就长时间没有回应)。我认为是IE对continuation的实现造成的,可能是如果有一个长连接开着,ie页面刷新时就会试图先等他超时了再获取新内容。 剩下的几个问题:
吃饭去了,回来贴代码~^_^ 烂MSDN,牛Wiki被几个API的声明搞得昏头转向半天。MSDN的定义居然跑不通,找到一个网站,上面几乎有全部的API在.Net下移植封装的声明。的确是厉害。搞定了回头看看才发现是个WiKi,怪不得能做出这么耗费人力的事情来。果然是众人拾柴火焰高啊~ 强烈推荐一下这个网站:PINVOKE.NET。顺路严重鄙视MSDN! 好玩的在Location Finder上面找自己……
My Windows Live Local Scratch Pad 以下是找到的从住的旅店到办公室的路径。嘿嘿。 Time Mile Instruction For Toward SUMMARY 赞M$的2005系列SQL Server 2005有了自定义聚类。
口水好久的东东啊……虽然相信用起来还是一如既往的烂~ PowerToy新功能完全图解教程
[Q] 使用该模块还有什么其他问题? 视频音乐模块完全图解教程
相关教程: HTML模块完全图解 界面设计模块完全图解 [Q] 视频音乐模块实现功能 [Q] 如何添加视频音乐模块 [Steps]
[Q] 如何使用该模块 A. 不可见 [Invisible] 选择该模式隐藏整个模块,一般用于播放音乐, 建议选中自动播放, 否则无法自动开始
[Q] 使用该模块还有什么其他问题? |
|
|