易栈网-膘叔

浏览模式: 标准 | 列表 | 图文
联合索引的经典例子

联合索引的经典例子

1.SQL需求,统计当天的数据量。

SQL> SELECT count(*) FROM test_union WHERE win_type=1 AND gmt_create >= trunc(sysdate,'dd') and gmt_create <= trunc(sysdate,'dd')+1;

COUNT(*)
----------
20063

1 row selected.

2.查看其索引,以gmt_create开头。

sql>create index idx_union on test_union (gmt_create,win_type) tablespace tbs_index compute statistics;

3.查看awr报表的性能,逻辑读很高,达到9700个。

Buffer Gets Executions Gets per Exec %Total Time Time (s) Hash Value
--------------- ---------- -------------- ------ -------- --------- ------
205,157,987 21,236 9,660.9 34.5 6733.21 7568.58 1532799124
Module: java@app12345 (TNS V1-V3)
SELECT count(*) FROM test_union WHERE win_type=1 AND gmt_create >= trunc(sysdate,'dd') and gmt_create <= trunc(sysdate,'dd')+1

因为是只通过索引扫描,当看到返回结果集在2万左右,我们很容易估算出这个sql需要的逻辑读,(gmt_date字段7个字节+win_type字段1个字节+rowid+…)*2万,小于100个,现在很明显是偏高的。
4.调整前我们先去看数据分布。

SQL> select win_type,count(*) from test_union group by win_type; --按win_type分组
win_type count(*)
---------- ----------
0 8583162
1 2725424
2 765237
3 2080156
4 2090871
5 3568682
SQL> select count(*) from test_union where gmt_create >= trunc(sysdate,'dd') and gmt_create <= trunc(sysdate,'dd')+1; --按gmt_create统计,一天数据量在22万左右

COUNT(*)
----------
229857

1 row selected.

5.调整索引,改为以win_type开头,为什么要以win_type开头呢?

create index idx_union123 on test_union (win_type,gmt_create) tablespace tbs_index compute statistics; --新索引

6.查看其执行计划,逻辑读变成了89。

sql>set auto traceonly
sql> select count(*) from test_union
where win_type=1 and gmt_create >= trunc(sysdate,'dd') and gmt_create <= trunc(sysdate,'dd')+1;

Elapsed: 00:00:07.17

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=1 Bytes=9)
1 0 SORT (AGGREGATE)
2 1 FILTER
3 2 INDEX (RANGE SCAN) OF 'idx_union123' (NO
N-UNIQUE) (Cost=7 Card=1 Bytes=9)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
89 consistent gets
0 physical reads
0 redo size
493 bytes sent via SQL*Net to client
655 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed

都在说建索引一定要看数据分布,从数据分布来看,一天的的数据(gmt_create)量在22万左右,而win_type的数据量非常 大,win_type为1有300万左右,为什么还要把win_type放在索引的前面呢?抛块砖,希望大家能对联合索引有更深入的理解,希望一起讨论。

--EOF--

帖子下的几个评论也很有意思:

1. 1棉花糖ONE

hehe,所以我在itpub上经常建议人家如果是建复合索引的话,把范围查询的谓词尽量放在后面,这样能增加木匠说的有效索引选择度,对oracle执行计划的判断有意义,当然更主要的是这样创建索引确实是减少了索引扫描的范围,不知道楼主的环境还在不在,根据数据来看,这2个字段是相关的,而且楼主恰好查询的是 count(*),可以不扫描表,走复合索引就比较正常,如果是select * from … ,这样很可能就不会选择走索引,就11g以前除了动态采样,加hint,统计信息上应该是搞不对,我测试了11g的扩展列的统计信息,感觉这玩意效果没有想象中的理想
Comment on Jan 10th, 2009 at 9:58 am
2. 2木匠

如果没有搞清楚SQL optimizer engine and Run time engine怎么工作,怎么使用composite index,
你的观测结果只是表面现象,永远也不知道为什么.

查看数据分布是对的,
但是这种情况是需要寻根问底的. my CA$0.02 + tax, 我的一点建议.

木匠不辞劳苦,再重复一遍SQL optimizer and runtime engine 是怎么工作的:

as soon as we have a range scan on a column used somewhere in the
middle of an index definition or fail to supply a test on such a column, the predicates on later columns do not restrict the selection of index leaf blocks that we have to examine.

In this case, the effective index selectivity has to be calculated from the predicate on just the “gmt_create” column. Because the test on “gmt_create” is range-based, the predicates on “win_type” do not restrict the number of index leaf blocks we have to walk.

关键字: effective index selectivity.
Comment on Jan 10th, 2009 at 1:49 am
3. 3丁原

这个案例已经很久了。
实际上是没有打算写的,这个例子一直有人在问为什么,问的多了,我干脆就邮件中的内容整理出来,大家 讨论加深印象。
我尝试把线上的数据拉下来做测试,发觉太大了根本拉不下来,只能是拼拼凑凑,gmt_create的索引已经drop掉了,部分测试数据可能不那么准确,但是数据还是在范围之内,不会偏差很大。

逻辑读怎么算?
select (7+1+rowid+..)*20000/8192,差不多就是我们要的逻辑读。

to木匠:cost不一定准确的,我更多的是查看数据分布,结果集,以逻辑读来判断。
to棉花糖:回复有审核的,防止很多垃圾广告。
Comment on Jan 9th, 2009 at 5:11 pm
4. 4carcase

win_type,gmt_create 扫描的索引块少多了,确定了win_type=1的范围后,再确定了是当天的数据
(索引是win_type,gmt_create 排序的,范围扫描马上能定位到当天的数据)也就扫描了2万多就够了 ,速度得到了提升。
和
gmt_create,win_type 扫描的索引块多了很多,相当于扫描了 gmt_create当天所有的数据了,22万多数据都要扫描一遍,过滤出win_type=1 的数据
Comment on Jan 9th, 2009 at 11:05 am
5. 5棉花糖ONE

Your comment is awaiting moderation.
到底咋回事啊
Comment on Jan 9th, 2009 at 10:45 am
6. 6棉花糖ONE

逻辑读89是不是搞错了
Comment on Jan 9th, 2009 at 10:45 am
7. 7jlttt

我们很容易估算出这个sql需要的逻辑读,(gmt_date字段7个字节+win_type字段1个字节+rowid+…)*2万,小于100个
(7+1+10+…)×2W 所读的块怎么算出小于100的?
Comment on Jan 9th, 2009 at 9:46 am
8. 8棉花糖ONE

to 木匠:
cardinality=1可能和9i有关,sysdate包含函数计算的时候,没有直方图的情况下,范围查询的选择度是按5%算的,2个5%*5%
Comment on Jan 9th, 2009 at 9:42 am
9. 9棉花糖ONE

如果把范围的放前面,索引的第二个字段只是起到filter的作用,并没有减少索引扫描的块,从索引的成本计算也能看出这一点
Comment on Jan 9th, 2009 at 9:36 am
10. 10木匠

对不起,再唠叨一句.

除了比较cost,还要比较Cardinality, 第二个SQL,index scan这一步的Card=1.
但是我觉得index scan这一步的Cardinality应该大于一, 不知道哪里出了问题.

每一个Oracle版本的Cost and Card会有不同的结果.9.2.0.6 and 10.1.0.4或更高版本会提供更有用的信息.

(木匠真够粗心大意的, 请包涵)
Comment on Jan 9th, 2009 at 2:26 am
11. 11木匠

In this case, the effective index selectivity has to be calculated from the predicate on just the “gmt_create” column. Because the test on “gmt_create” is range-based, the predicates on “win_type” do not restrict the number of index leaf blocks we have to walk.

照搬老刘(Lewis)的原话, 改一下列名就行了. :)

上一个评论中的cost 指的是整个SQL(with index scan)的cost.

另外, 你忘了记录走第一个索引的SQL执行计划,可以比较一下 INDEX (RANGE SCAN) 的 cost.

INDEX (RANGE SCAN) OF ‘idx_union123′ (NON-UNIQUE) (Cost=7 Card=1 Bytes=9), 我们看到走第二个索引的(index range scan) cost=7.
Comment on Jan 9th, 2009 at 2:03 am
12. 12木匠

Q: 为什么还要把win_type放在索引的前面呢?
A: leaf_blocks * effective index selectivity

第一个索引, range scan on gmt_create, first column on index.
Because as soon as we have a range scan on a column used somewhere in the
middle of an index definition or fail to supply a test on such a column, the predicates on later columns do not restrict the selection of index leaf blocks that we have to examine.

参考:
cost =
blevel +
ceiling(leaf_blocks * effective index selectivity) +
ceiling(clustering_factor * effective table selectivity)

a well-known guideline for arranging the columns in a multicolumn
index. Columns that usually appear with range-based tests should generally appear later in the index than columns that usually appear with equality tests. Unfortunately, changing the column ordering in an index can have other contrary effects, which we will examine in Chapter 5.

Now you got test case. very well ! ^_^

Reference: page 74,62,67 of book Cost-Based Oracle Fundamentals
Comment on Jan 9th, 2009 at 1:52 am

接口与抽象类【来自博客园】

什么是接口 (interface) ?

接口是方法的抽象,如果不同的类有同样的方法,那么就应该考虑使用接口。
(1)接口是一个行为的规范、协议。其实就是类和类之间的一种协定,一种约束
(2)C#不支持多继承,但是他把这个功能交给接口来实现。
(3)类与类之间的系统资源调用方式不一样,导致他们之间的通信很困难,而接口可以屏蔽掉它们之间的差异,能使他们顺利通信。

什么是抽象类(abstract class)?

1. 抽 象类仅提供一个类型的部分实现。抽象类可以有实例变量,以及一个或多个构造函数。抽象类可以同时有抽象方法和具体方法。一个抽象类不会有实例,这些构造函 数不能被客户端调用来创建实例。一个抽象类的构造函数可以被其子类调用,从而使一个抽象类的所有子类都可以有一些共同的实现,而不同的子类可以在此基础上 有其自己的实现。

2. 抽象类的用途1) 具体类不是用来继承的。 Scott Meyers曾指出,只要有可能,不要丛具体类继承。2) 假设有2个具体类,类A和类B,类B是类A 的子类,那么一个最简单的修改方案是应当建立一个抽象类(或java接口)C,然后让类A和类B成为抽象类C的子类。3) 抽象类应当拥有尽可能多的共同代码。以提高代码的复用率。4) 抽象类应当拥有尽可能少的数据。

3. 基于抽象类的模式和原则1) 针对抽象编程,不要针对具体编程。2) 尽量使用合成(Composition),而不要使用继承来达到复用的目的。3) 使用摸板方法模式

4. 什么时候应当使用继承复用1) 子类是超类的一个特殊种类,而不是超类的一个角色,也就是要区分”is – a” 和“has-a”两种关系。2) 永远不会出现需要将子类换成另一个子类的情况。如果设计师不是很肯定一个类回不会在将来变成另一个类的子类的话,就不应当把这个类设计成这个超类的子类。 3) 子类具有扩展超类的责任,而不是具有置换掉(Override)或注销掉(Nullify)超类的责任。4) 只有在分类学上有意义时,才可以使用继承,不要丛工具类继承。

抽象方法是必须实现的方法。且只能在抽象类中。

接口与抽象类

一个类可以继承多个接口。。。
一个类只能继承一个抽象类。。。

抽象方法是必须实现的方法。就象动物都要呼吸。但是鱼用鳃呼吸,猪用肺呼吸。
动物类要有呼吸方法。怎么呼吸就是子类的事了。

现在有很多讨论和建议提倡用interface代替abstract类,两者从理论上可以做一般性的混用,但是在实际应用中,他们还是有一定区别的。抽象类一般作为公共的父类为子类的扩展提供基础,这里的扩展包括了属性上和行为上的。而接口一般来说不考虑属性,只考虑方法,使得子类可以自由的填补或者扩展接口所定义的方法,就像JAVA王子所说的事件中的适配器就是一个很好的应用。
用一个简单的例子,比如说一个教师,我们把它作为一个抽象类,有自己的属性,比如说年龄,教育程度,教师编号等等,而教师也是分很多种类的,我们就可以继承教师类而扩展特有的种类属性,而普遍属性已经直接继承了下来。
而接口呢~还是拿教师做例子,教师的行为很多,除了和普通人相同的以外,还有职业相关的行为,比如改考卷,讲课等等,我们把这些行为定义成无body的方 法,作为一个集合,它是一个interface。而教师张三李四的各自行为特点又有不同,那么他们就可以扩展自己的行为body。从这点意义上来说,interface偏重于行为。
总之,在许多情况下,接口确实可以代替抽象类,如果你不需要刻意表达属性上的继承的话。

原文:http://www.cnblogs.com/cyc09156/archive/2009/01/09/1372330.html

经典格斗游戏《街头霸王4》试玩截图

经典格斗游戏《街头霸王4》试玩截图

由CAPCOM负责开发的格斗游戏《街头霸王4》(Street Fighter 4)最新试玩截图公布,本作将承袭系列作传统2D玩法,并采用最新的3D绘图技术,以更华丽的方式重现原作独特的2D绘图风格。PC版发售日期未 定,XBOX360/PS3即将2月17日发售。

另外,CAPCOM透露将推出内含限量非卖版人偶模型(PS3 版为隆,Xbox 360 版为深红毒蛇)、Studio 4C 制作之65分钟高分辨率动画影片(PS3版为BD蓝光影片,Xbox 360 版为专用高分辨率DVD影片,非一般 DVD 影片)、原声配乐 CD、攻略指引手册与独家下载内容的典藏版,定价 79.99美元。

以上内容来自于:http://www.lingaoyi.com/screens/new-street-fighter-4-screenshots/,同时以下图片也来自于该站(我只取了两张与我有关的图片)

更多图片请去该网站查看。。。

4806216ee58b61a04913d5641fe5eb26

febc9695e02ca60f3656d57f4ad0564c

TP1.5版本中使用smarty模版引擎的技巧

TP1.5版本中使用smarty模版引擎的技巧

从TP1.5开始,对于其他的模版引擎有了原生支持(不再是以前那种插件机制了)。
本文以使用smarty模版为例作点简单介绍,其他的,可以参考一下View.class.php中的fetch方法可知。

TP的SVN中已经含有smarty模版库,因此当你要使用的时候,只需要在项目的config.php里作一点简单的配置:

'TMPL_ENGINE_TYPE' => 'smarty', //这个是设置引擎为smarty


从2009-01-07下午的SVN版本里,流年为又增加了一个TMPL_ENGINE_CONFIG这个数组,即:

'TMPL_ENGINE_CONFIG' = array(
'template_dir' => TMPL_PATH , //这个就是tpl目录了
'compile_dir' => CACHE_PATH . "tplCompile/", //这是我自己设定的,模版编译缓存放在这个目录里
'cache_dir' => CACHE_PATH . "tplCache/", //如果需要生成页面缓存,这个也是必须的
'left_delimiter' => '{',
'right_delimiter' => '}',
'caching' => false,
'force_compile' => true,
'compile_check' => true,
),


备注:如果按照我这样的写法,请到cache目录里手动创建tplCompile和tplCache两个目录,否则程序会报错。
报错信息大致为:

Catchable fatal error: Object of class Smarty could not be converted to string in D:\local\htdocs\ThinkPHP\Album\Temp\~runtime.php on line 145


如果出现这样的报错信息,请先检查这两个目录是否存在

在项目开发的时候,caching 最好设为false,否则你根本看不到效果。

如此设定完毕后,你就可以直接在项目中使用了,下面以Index模块的index方法进行举例:在IndexAction.class.php的index的方法里

$this->assign("test" , "This is a test string");
$this->display();

然后到模版里:

{$test}

就可以看到输出了。不过,这里需要注意的是,如果你$this->display()没有指定文件名,那么默认的模版文件就是default/Index/index.html,这点和原先使用TP默认的模版引擎没有什么区别。
出现问题最大的应该是在include方法里,include的使用方法是:{include file="Public/header.html"},就象我前面所说,smarty的模版路径只指到了tpl目录,但实际上,我们是在默认的default目录下操作,因此正确的写法应该是{include file="default/Public/header.html"}
如果我们写的程序要对应多模版,那么,上面那种直接写死default的方法是不行的,还好,TP为我们留了一个常量:TEMPLATE_NAME,于是我们的写法就可以是现在这样:

{include file="`$smarty.const.TEMPLATE_NAME`/Public/header.html"}

现在试一下,是否公用目录里的header.html被加载了?

最后再报一个warning。如果是在WINDOWS下面开发并且开启了DEBUG_MODE,那么你在读取模版的时候,页面的Trace信息里,偶尔会出现一个注意:

[ 09-01-08 14:36:02 ] 注意:[2] unlink(./Cache/tplCompile/\%%70^706^706C3AFE%%index.html.php) [function.unlink]: No such file or directory core.write_file.php 第 44 行.


这些信息,可以被忽略掉。模版文件还是会正常的生成和编译的。它只会在第一次生成模版编译文件的时候出现

MYSQL数据库中取得同一字符串出现的次数

在PHP中,取得一个字符串中某一个字符出现的次数那是相当的简单的呀,方法也有N种,再不济我还可以把这个字符当成分隔标记进行分隔,最后求分隔出来的数组的长度就行了。

但MYSQL不行呀,没有这样的函数,还好,网上很多人给出了方案,我这里贴个最简单的:

原理:(用原始字符串的总长度 减去 将原始字符串中要统计的字符串去除后的字符串长度)再除以 要统计的字符串长度

SELECT ( char_length( 'test' ) - char_length( replace('test', 't' , ''))) / (char_length('t'));
---- 注:test为原始字符串,t为要统计的字符串
Web通信分析工具(ZZ)

Web通信分析工具(ZZ)

身为一名WEB开发人员,对于WEB通信当然是非常关心的,对于每一个POST甚至都有可能要抓包 进行分析,但究竟怎么做,用什么做,如何去做,还是有点讲究的。我个人是习惯用fireBug和webdeveloper还有IE下的httpwatch等进行分析。看到这篇文章的介绍,也可以看看别人是怎么做,取别人的之长补自己之短才能有进步嘛。。

原文链接:http://tech.idv2.com/2008/12/30/web-comm-analyzer/

原文作者:charlee

内容如下:(图片下载到了本地了)

在抓虾上看到一篇Web开发分析工具的文章(链接就免了),怎么远没有我用的东西好用呢? 还是介绍介绍我用的吧。由于平常开发只用FireFox,完成后再去调试IE, 所以这些工具绝大部分是针对FireFox的。

如果把Web通信从上到下分为许多层——XMLHttpRequest层,HTTP层,TCP层, 那么这些工具可以分别抓取每个层的通信数据进行分析,结合使用极其强大。

2008/12/31:另外可以参考daniel同学的Web开发常用工具一文,相信会大有帮助哦。

XMLHttpRequest层:Firebug

适用范围 Ajax应用程序
优点 使用方便,数据截取完整
缺点 只能分析XMLHttpRequest请求,其他类型的请求无能为力

Firebug应该是尽人皆知了。 它的控制台能监视XMLHttpRequest请求,能看到完整的请求和应答的数据。 用它来调试Ajax程序是最好不过了。

24c055168705a106f9e14d65b0569997

HTTP层:Tamper Data

适用范围 普通网页,Ajax应用程序,Flash
优点 使用方便,适用范围广,任何HTTP请求都能截获
缺点 只能截获请求头、请求内容、应答头,得不到应答内容;涉及文件下载时效率大幅度降低

Tamper Data比Firebug进了一步, 只要是HTTP请求,它都能抓下来,可惜的是看不到应答内容。 适用于分析请求流程、请求参数、请求数据、重定向URL。 对于非Ajax程序如普通网页、Flash、ActiveX等程序,用Tamper Data来分析十分方便。

5b126c6f2ef3f8092639de8b40da4415

HTTP层:burpsuite

适用范围 普通网页,Ajax应用程序,Flash
优点 适用范围广,截取数据完整,不挑网卡
缺点 使用稍稍麻烦

burpsuite中的proxy功能用于分析Web通信十分好用。 它的原理是架设一个代理服务器,让浏览器通过代理来发送请求,代理就可以截获数据了。

使用方法为:

  1. 配置proxy,然后设置浏览器使用它的proxy
  2. 访问想要抓取的那个网页
  3. burp suite的proxy中就会看到请求内容,在这里即可详细地分析请求。
  4. 如果想继续分析应答,可以右键点击请求内容,选send to repeater
  5. 切换到repeater标签,点【go】按钮发送请求,在下方就可以看到应答

TCP层:wireshark

适用范围 任何网络程序
优点 适用范围广,截取数据完整
缺点 使用麻烦;不能使用loopback网卡

如果以上方法都不管用,就要祭出终极武器wireshark(原名ethereal)了。 它从网络的最底层入手,可以截获任何类型的网络通信,而不仅仅是HTTP协议。 比如要开发一个邮件程序,需要分析服务器端脚本与POP3服务器之间的通信, 那就非得wireshark出马不可了。

8e0cb4704b89279ff950a47d6d9a3169

使用方法:

  1. 在wireshark中选择抓取物理网卡;
  2. 让应用程序发请求;
  3. 在wireshark中停止抓取;
  4. 从抓到的包一览中找出刚才应用程序发出的请求,右键点击选择 Follow TCP Stream,就能看到该请求的完整内容。

这个工具的不足之处是它不能抓取loopback的网卡,也就是说, 如果你的程序连接的是位于localhost或127.0.0.1的服务器, 那wireshark是抓不到的。解决方法是,让程序通过真实物理网卡去连别的机器, 或是使用虚拟机的虚拟网卡也行。

Windows下SVN服务器的架设

Windows下SVN服务器的架设

看完这篇文章后,两个字:郁闷。
理由是我刚刚装完visualSVN,结果就看到这篇文章了,由于已经有三个人在用这个SVN,想再更换也不方便,所以就不再更换了。不过文章还是要转一下的,作个备份还是很重要的,下次再换成标准的SVN服务器。
原文链接:http://www.phpweblog.net/fuyongjie/archive/2009/01/05/6265.html(由于原文也是转摘的,但我不知道最初的地址,只能用这个地址了)
原文如下:
作者: rocksun  来源:Subversion

Subversion安装成service

以前的svnserve要想成为windows服务,必须依赖于svnservice或其他工具。从Subversion1.4开始,Subversion本身就集成Windows服务的工具。

1,安装svnservice

在Windows NT中(包括Windows XP, Windows 2000, Windows 2003 Server)本身包含了一个安装服务的工具,叫做"Service Control",也就是sc.exe。

例如我的Subversion安装在"D:\Subversion",版本库在"D:\svnroot",而我希望对应的Subversion服务名为svnservice,安装这个svn服务的命令就可以这样写:

sc create svnservice
binpath= "D:\Subversion\bin\svnserve.exe --service -r D:\svnroot"
displayname= "SVNService"
depend= Tcpip

请注意,因为便于察看,上面的命令分为多行,但在实际执行时应该在一行里。另外,在以前启动svnserve时会使用"-d"选项,也就是守护进程模式,在这里不能使用,会导致服务无法启动。同样,"-i"和"-t"选项也不能使用。

在命令行窗口执行完这个命令之后,服务还没有启动,你可以继续运行"net start svnservice"启动这个服务,然后使用"net stop svnservice"停止服务。

另 外还有两点需要小心处理。首先,如果路径中包括空格,一定要用“\”处理“"”号,例如上面的例子中如果svnserve.exe在“c: \program files\subversion\”中,则命令应该写为“binpath= "\"c:\program files\subversion\bin\svnserve.exe\"”(“”中的内容),整个命令如下,红色部分是改变部分:

sc create svnservice
binpath= "\"D:\program files\Subversion\bin\svnserve.exe\" --service -r D:\svnroot"
displayname= "SVNService"
depend= Tcpip

其次,sc对选项的格式还有要求,例如“depend= Tcpip”不能写为“depend = Tcpip”或“depend=Tcpip”,也就是“=”前不能有空各,而后面必须有空格。

2,删除服务

如果服务安装的有问题,你可能需要删除服务。要删除前面添加的服务,只需要运行"net start svnservice","svnservice"就是我们创建服务时使用的名字。

3,配置服务是自动启动

默认情况下安装的服务不会随Windows的启动而启动,为了使svn服务能够随Windows启动而启动,需要修改一下"sc create"命令(首先要删除),增加"start= auto"选项:

sc create svnservice
binpath= "D:\Subversion\bin\svnserve.exe --service -r D:\svnroot"
displayname= "SVNService"
depend= Tcpip
start= auto

当然你也可以使用图形化的工具修改服务的属性,你可以在“开始->运行...”中执行"services.msc",然后在界面中修改。

Subversion的权限控制

1,认证(Authentication)和授权(Authorization)


这两个术语经常一起出现。其中认证的意思就是鉴别用户的身份,最常见的方式就是使用用户名和密码,授权就是判断用户是否具备某种操作的权限,在 Subversion里提供了“authz-db”文件,实现了以路径为基础的授权,也就是判断用户是否有操作对应路径的权限,在Subversion 1.3之后,svnserve和Apache一样都可以使用“authz-db”文件。

2. svnserve下的配置文件

因为本文是以svnserve为例的,所以先介绍一下版本库目录的结构:

D:\SVNROOT\PROJECT1
├─conf
├─dav
├─db
│ ├─revprops
│ ├─revs
│ └─transactions
├─hooks
└─locks

其中conf下面有三个文件:

authz
passwd
svnserve.conf

其中的“svnserve.conf”是这个版本库的配置文件,当使用svnserve时,这个配置文件决定了使用什么认证和授权文件:

password-db = passwd
authz-db = authz

上面的配置说明使用“svnserve.conf”同目录的passwd和authz,其中的password-db指定了用户密码文件,authz-db是我们的授权文件,也就是我们本文主要介绍的文件。

注意:使用Apache作为服务器时,根本就不会参考“svnserve.conf”文件的内容,而是会参考Apache的配置。

3,基于svnserve的版本库文件布局

使用svnserve时,为了管理的方便,应该使用相同的认证和授权文件,所以应该让所有版本库的配置文件svnserve.conf指向同一个password-db和authz-db文件。下面是一个多版本库的目录:

D:\SVNROOT
├─project1
│ ├─conf
│ ├─dav
│ ├─db
│ │ ├─revprops
│ │ ├─revs
│ │ └─transactions
│ ├─hooks
│ └─locks
└─project2
├─conf
├─dav
├─db
│ ├─revprops
│ ├─revs
│ └─transactions
├─hooks
└─locks

D:\SVNROOT下有两个目录project1和project2,都已经创建了版本库,所以我们修改每个conf目录下的svnserve.conf,使之指向同一个password-db和authz-db文件。

password-db = ..\..\passwd

authz-db = ..\..\authz这样,D:\SVNROOT\passwd和D:\SVNROOT\authz就控制了所有版本库的svnserve访问。另外在 后面的操作中要关闭匿名访问,应该去掉“anon-access = none”前的“#”号,保证只有认证用户可以访问。

注意:还有一点需要注意,那就是svnserve的“realm”的值,在上面的设置下,应该保证所有的版本库使用相同的realm值,这样,对版本库的密码缓存可以在多个版本库之间共享,更多细节见客户端凭证缓存。

4,测试用户和组说明

版本库禁止任何匿名用户的访问,只对认证用户有效。

root:配置管理管理员,对版本库有完全的管理权限。

p1_admin1:project1的管理员,对project1有完全权限。
p1_d1:project1的开发者,对project1的trunk有完全的权限,但是对其中的/trunk/admin目录没有任何权限。
p1_t1:project1的测试者,对project1的trunk有完全的读权限,但是对其中的/trunk/admin目录没有任何权限。

p2_admin1:project2的管理员,对project2有完全权限。
p2_d1:project2的开发者,对project2的trunk有完全的权限,但是对其中的/trunk/admin目录没有任何权限。
p2_t1:project2的测试者,对project2的trunk有完全的读权限,但是对其中的/trunk/admin目录没有任何权限。

对应的组及组的用户:

p1_group_a:p1_admin1
p1_group_d:p1_d1
p1_group_t:p1_t1
p2_group_a:p2_admin1
p2_group_d:p2_d1
p2_group_t:p2_t1

5,修改D:\SVNROOT\passwd文件

前面已经说过了,用户和密码文件应该是在D:\SVNROOT\passwd,所以我们为每一位用户设置权限,文件内容如下:

[users]
p1_admin1 = p1_admin1
p1_d1 = p1_d1
p1_t1 = p1_t1

p2_admin1 = p2_admin1
p2_d1 = p2_d1

p2_t1 = p2_t1为了便于验证,所有密码和用户名一致,如果你使用的是其他认证方式,这一步可能不同,但是用户名应该都是一样的。

6,配置授权,修改D:\SVNROOT\authz

[groups]
# 定义组信息

p1_group_a = p1_admin1
p1_group_d = p1_d1
p1_group_t = p1_t1

p2_group_a = p2_admin1
p2_group_d = p2_d1
p2_group_t = p2_t1

[/]
# 指定所有的版本库默认只读,root可读写
* = r
root = rw

[project1:/]
# 指定对版本库project1根目录的权限
@p1_group_a = rw
@p1_group_d = rw
@p1_group_t = r

[project1:/trunk/admin]
# 指定对版本库project1的/trunk/admin根目录的权限,
# p1_group_a读写,p1_group_d和p1_group_t没有任何权限。
@p1_group_a = rw
@p1_group_d =
@p1_group_t =

[project2:/]
# 指定对版本库project2根目录的权限
@p2_group_a = rw
@p2_group_d = rw
@p2_group_t = r

[project2:/trunk/admin]
# 指定对版本库project1的/trunk/admin根目录的权限
@p2_group_a = rw
@p2_group_d =
@p2_group_t =

经过以上设置以后,你会发现一些有趣的事情。当使用用户“p1_d1”,检出project1的trunk时,目录是空的,好像admin目录根本不存在一样,当使用p1_d1用户浏览版本库时,能够看到admin目录,但是其中的内容却无法看到。

关 于中文目录,也是没有问题的,只是注意要把authz文件转化为UTF-8格式,在我的WINXP的UltraEdit里显示的文件格式为U8-DOS, 具体的做法是用UltraEdit打开authz文件,然后选择“文件->转换->ASCII转UTF-8”,然后保存。

再复杂的情况也不过如此,在实际的工作中要首先规划好权限,只赋给用户最小的权限,保证以最小的配置实现最复杂的权限控制。

Subversion备份

版本控制最关键的一件事是保证数据的安全性,不能因为磁盘损坏,程序故障造成版本库无可挽回的错误,为此必须制定较完备的备份策略。在Subversion中,我们有三种备份方式:完全备份,增量备份和同步版本库。

1, 完全备份

最常见和简单的备份就是直接使用拷贝命令,将版本库目录拷贝到备份目录上,就可以了。但是这样不是很安全的方式,因为如果在拷贝时版本库发生变化,将会 造成备份的结果不够准确,失去备份的作用,为此Subversion提供了“svnadmin hotcopy”命令,可以防止这种问题。

还记得我们的版本库目录吗?

D:\SVNROOT
├─project1
│ ├─conf
│ ├─dav
│ ├─db
│ │ ├─revprops
│ │ ├─revs
│ │ └─transactions
│ ├─hooks
│ └─locks
└─project2
├─conf
├─dav
├─db
│ ├─revprops
│ ├─revs
│ └─transactions
├─hooks
└─locks

如果要把project1备份到d:\svnrootbak目录下,只需要运行:

svnadmin hotcopy d:\svnroot\project1 d:\svnrootbak\project1

但是我们作为配置管理员,必须想办法优化这个过程,如果我们这个目录下有许多版本库,需要为每个版本库写这样一条语句备份,为此我写了下面的脚本,实现备份一个目录下的所有版本库。我们在D:\SVNROOT下创建了两个文件,simpleBackup.bat:

@echo 正在备份版本库%1......
@%SVN_HOME%\bin\svnadmin hotcopy %1 %BACKUP_DIRECTORY%\%2
@echo 版本库%1成功备份到了%2!

这个文件仅仅是对“svnadmin hotcopy”的包装,然后是backup.bat:

echo off

rem Subversion的安装目录
set SVN_HOME="D:\Subversion"

rem 所有版本库的父目录
set SVN_ROOT=D:\svnroot

rem 备份的目录
set BACKUP_SVN_ROOT=D:\svnrootbak

set BACKUP_DIRECTORY=%BACKUP_SVN_ROOT%\%date:~0,10%
if exist %BACKUP_DIRECTORY% goto checkBack
echo 建立备份目录%BACKUP_DIRECTORY%>>%SVN_ROOT%/backup.log

mkdir %BACKUP_DIRECTORY%

rem 验证目录是否为版本库,如果是则取出名称备份
for /r %SVN_ROOT% %%I in (.) do @if exist "%%I\conf\svnserve.conf" %SVN_ROOT%\simpleBackup.bat "%%~fI" %%~nI
goto end

:checkBack
echo 备份目录%BACKUP_DIRECTORY%已经存在,请清空。
goto end

:end

你 在使用的时候,只需要修改backup.bat开头的三个路径,将两个脚本拷贝到“SVN_ROOT”下就可以了。根据以上的配置,你只需要运行 backup.bat,就可以把“SVN_ROOT”下的版本库都备份到“BACKUP_SVN_ROOT”里,并且存放在备份所在日的目录里,例如 “D:\svnrootbak\2006-10-22”。

虽然这部分工作很简单,可是必须有人定时地去执行这个操作(例如每周一凌晨),为了避免发生遗忘的情况,我们可以将这个操作加入到系统的at任务当中去,例如还是上面的环境,为了安装at任务,我们运行:

at 1:00 /every:M D:\svnroot\backup.bat这样在每周一凌晨1:00都会执行这个备份过程。当然备份在本机也是不安全的,你也许需要上传到别的机器,这个就要靠你自己去实现了。

2, 增量备份

尽管完全备份非常简单,但是也是有代价的,当版本库非常巨大时,经常进行完全备份是不现实的,也并不必要,但是一旦版本库在备份之间发生问题,该如何呢,这里我们就用到了增量备份。

增量备份通常要与完全备份结合使用,就像oracle数据库的归档日志,记录着每次Subversion提交的变化,然后在需要恢复时能够回到最新的可用状态。在我们这个例子中我们使用的是,svnadmin dump命令进行增量的备份,使用方法是:

svnadmin dump project1 --revision 15 --incremental > dumpfile2

上面的命令实现了对修订版本15进行增量的备份,其中的输出文件dumpfile2只保存了修订版本15更改的内容。

为了记录每次提交的结果,我们需要使用一项Subversion的特性--钩子(hook),看看我们的project1目录:

├─project1
│ ├─conf
│ ├─dav
│ ├─db
│ │ ├─revprops
│ │ ├─revs
│ │ └─transactions
│ ├─hooks
│ └─locks

其中的hooks目录里存放的就是钩子脚本,我们在此处只使用post-commit钩子,这个钩子会在每次提交之后执行,为了实现我们的备份功能,我们在hooks下建立一个文件post-commit.bat,内容如下:

echo off
set SVN_HOME="C:\Program Files\Subversion"
set SVN_ROOT=D:\svnroot
set UNIX_SVN_ROOT=D:/svnroot
set DELTA_BACKUP_SVN_ROOT=D:\svnrootbak\delta
set LOG_FILE=%1\backup.log
echo backup revision %2 >> %LOG_FILE%
for /r %SVN_ROOT% %%I in (.) do if D:/svnroot/%%~nI == %1 %SVN_ROOT%\%%~nI\hooks\deltaBackup.bat %%~nI %2
goto end
:end

通过这个脚本,可以实现D:\svnroot下的版本库提交时自动增量备份到D:\svnrootbak\delta(确定这个目录存在),其中使用的deltaBackup.bat其实可以放在任何地方,只是对脚本的svnadmin dump的包装,内容如下:

@echo 正在备份版本库%2......
%SVN_HOME%\bin\svnadmin dump %SVN_ROOT%\%1 --incremental --revision %2 >> %DELTA_BACKUP_SVN_ROOT%\%1.dump
@echo 版本库%2成功备份到了%3!

以上两个脚本可以直接拷贝到project2的hooks目录下,不需要修改就可以实现project2的自动备份。

以上的操作已经OK了,现在需要做的是将完全备份和增量备份结合起来,也就是在完全备份后清理增量备份的结果,使之只保存完全备份后的结果。

当果真出现版本库的故障,就要求我们实现版本库的恢复操作了,这是用要使用svnadmin load命令,同时也需要上次的完全备份例如要把上次完全备份backuprepo,和之后的增量备份dumpfile:

svnadmin load backuprepo < dumpfile

最后的结果,可以下载svnroot.rar,将之解压缩到d:\下,然后修改几个bat文件的SVN_HOME就可以使用了。

3, 版本库同步

Subversion 1.4增加了同步机制,可以实现一个版本库同另一个版本库的同步(但好像只是单向的),我们可以用来实现版本库的备份或镜像。

3.1. 对目标库初始化

svnsync init svn://localhost/project2 svn://localhost/project1
其中project2是目标的版本库,而project1是源版本库。其中的目标版本库必须为空,而且必须允许修订版本属性的修改,也就是在目标的版本 库的hooks目录里添加一个文件pre-revprop-change.bat,内容为空即可。

3.2. 同步project2到project1

svnsync sync svn://localhost/project2
这时候你update一下你的project2的一个工作拷贝,就会发现有了project1的所有内容。如果project1又有提交,这时候 project2的版本库无法看到最新的变化,还需要再运行一遍sync操作,这样才能将最新的变化同步。需要注意的是,目标版本库只能做成只读的,如果 目标版本库发生了变更,则无法继续同步了。

3.3. 同步历史属性的修改

因为同步不会更新对历史属性的修改,所以svnsync还有子命令copy-revprops,可以同步某个版本的属性。

3.4. 钩子自动同步

希望在每次提交时同步,则需要在源版本库增加post-commit脚本,内容如下:

echo off
set SVN_HOME="D:\Subversion"
%SVN_HOME%\bin\svnsync sync --non-interactive svn://localhost/project2

把以上内容存放为post-commit.bat,然后放到版本库project1下的hooks目录下,这样project1每次提交,都会引起project2的同步。

项目小结

项目小结

项目要求写小结,其实我也很奇怪,不过想想这是领导安排的任务,这还是要完成的。

项目从10月份开始,1月1日上线,共经历了整整 三个月,在这三个月内,我深刻贯彻项目小组领导的精神,按照需求根据进度逐步开展代码的编写工作。

在项目初期需求尚未明确的时候,领导给予了我们充分的授权,但这也使得我们在没有人带领下而出现了迷惘。正所谓,大海航行靠舵手,领导在中后期果断的介入了项目,使得我们在迷惘的灰雾里找到方向。

正是由于领导的后期介入,我们在需求分析、系统评估、代码编写、小组协作方面得到了充分的支持,使得项目的进展极其顺利,最终我们在项目上线前一星期完成了编码工作,并留下了一周的充分时间交由测试小组进行测试并进行程序的BUG修复工作。

在这次的项目过程中,我充分学习到了如何进行小组协作,并在小组协作方面有了一定的心得,对于项目的控制、协调也有了一定的了解。在小组协作的时候,PHP与JAVA代码之间的通讯、协议的制定等也有了长足的进步,可以毫不夸张的说,在下次进行协作的时候,小组之间的联调效率可以上升100%左右。

在项目的进行中,也曾因为理念不同以及小组协作方面与领导产生一定的分歧,但由于领导的大度使得这些问题最终都得到了解决。同时为了照顾加班的同志,领导与加班人员共进退,来得最早走的最晚,使得在加班的时候不显得孤独。

通过三个月的项目进程,使得我在代码编写能力、小组协作能力、进度管理经验、需求分析等方面有了极大的提高,迫切希望下一次项目的到来,可以让我在公司能够学到更多的知识。

simplexml简单测试

simplexml简单测试

simplexml函数是PHP5推出的一个新的类库,可以让你很方便的操作XML,对大多数人而言,最常用的应该是simplexml_load_file和simple_load_string两种了。

但是在使用中总会有一点问题,于是做了简单的一些测试。

建立了四个文件,两个GBK,两个UTF8,

两个GBK的文件内容分别如下:

<?xml version="1.0" encoding="gbk" ?>
<test>
<test1>this is a test中文</test1>
</test>
<?xml version="1.0" encoding="utf-8" ?>
<test>
<test1>this is a test中文</test1>
</test>
正如你们想的那样,UTF8的文件也是这些内容。

然后开始使用 simplexml_load_file进行测试:

<?php
$xmlFile = '1.txt';

$xml = @simplexml_load_file( $xmlFile );
if ( $xml === false ){
echo( 'failed' );
}else{
echo '<pre>';
print_r( $xml );
echo '</pre>';
}

?>
然后,你会发现什么?

1、如果xml的encoding与文件类型不符,立刻报错
2、simplexml可以处理gbk的XML,即文件内容和encoding都为gbk(ANSI格式)
3、不管文件内容是什么,处理完了,都是UTF8.

over,测试完毕


其实想这个测试还有另外一个原因,因为thinkPHP的模版引擎的自定义扩展是采用XML来实现的,以前很多朋友在扩展的时候都会报错,那时候,我们的提示是:请使用 UTF8格式创建XML,现在,我们又可以提示:随便你想怎么创建,只要您的文件格式和encoding一致就行。

五种常见的 PHP 设计模式

五种常见的 PHP 设计模式

设计模式只是为 Java™ 架构师准备的 —— 至少您可能一直这样认为。实际上,设计模式对于每个人都非常有用。如果这些工具不是 “架构太空人” 的专利,那么它们又是什么?为什么说它们在 PHP 应用程序中非常有用?本文解释了这些问题。

设计模式 一书将设计模式引入软件社区,该书的作者是 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides Design(俗称 “四人帮”)。所介绍的设计模式背后的核心概念非常简单。经过多年的软件开发实践,Gamma 等人发现了某些具有固定设计的模式,就像建筑师设计房子和建筑物一样,可以为浴室的位置或厨房的构造方式开发模板。使用这些模板或者说设计模式 意味着可以更快地设计更好的建筑物。同样的概念也适用于软件。

设计模式不仅代表着更快开发健壮软件的有用方法,而且还提供了以友好的术语封装大型理念的方法。例如,您可以说您正在编写一个提供松散耦合的消息传递系统,也可以说你正在编写名称为观察者 的模式。

用较小的示例展示模式的价值是非常困难的。这往往有些大材小用的意味,因为模式实际上是在大型代码库中发挥作用的。本文不展示大型应用程序,所以您 需要思索的是在您自己的大型应用程序中应用示例原理的方法 —— 而不是本文演示的代码本身。这不是说您不应该在小应用程序中使用模式。很多良好的应用程序都以小应用程序为起点,逐渐发展到大型应用程序,所以没有理由不 以此类扎实的编码实践为基础。

既然您已经了解了设计模式以及它们的有用之处,现在我们来看看 PHP V5 的五种常用模式。

出于版权问题,我不能继续往下贴。不过原文也很方便的可以被访问到。

原文:http://www.ibm.com/developerworks/opensource/library/os-php-designptrns/index.html
原文译文:http://www.ibm.com/developerworks/cn/opensource/os-php-designptrns/index.html