PHP程序编码规范

PHP程序编码规范标准 版本号:v2.0 前言 通过建立代码编写规范,形成开发小组编码约定,提高程序的可靠性、可读性、可修改性、可维护性、一致性,保证程序代码的质量,继承软件开发成果,充分利用资源。提高程序的可继承性,使开发人员之间的工作成果可以共享。 建立代码编写规范会有以下优点: 1.程序员可以了解任何代码,弄清程序的状况 2.新人可以很快的适应环境 3.防止新接触php的人出于节省时间的需要,自创一套风格并养成终生的习惯 4.防止新接触php的人一次次的犯同样的错误 5.在一致的环境下,人们可以减少犯错的机会 6.程序员们有了一致的敌人 编码应遵循以下基本原则: 1.遵循开发流程,在设计的指导下进行代码编写。 2.代码的编写以实现设计的功能和性能为目标,要求正确完成设计要求的功能,达到设计的性能。 3.程序具有良好的程序结构,提高程序的封装性好,减低程序的耦合程度。 4.程序可读性强,易于理解;方便调试和测试,可测试性好。 5.易于使用和维护,良好的修改性、扩充性;可重用性强/移植性好。 6.占用资源少,以低代价完成任务。 7.在不降低程序的可读性的情况下,尽量提高代码的执行效率。 以下为对本规范的详细说明: 1. 前端开发脚本语言代码规范: @版本4.0 030403 完全实现EP化个性使用原则! @版本3.0 030102 升级版本号 @版本2.1 021116 修改版本号 @版本 2.0 021114 建立文件号 @模块功能:(简单介绍) @作者: @作者E-Mail: @项目主管: @开发日期: Web Define Scheme 1.1.1命名约定 建议依从各种主流语言的命名原则。 从前端的特性强烈建议以下: 尽量保持命名习惯,不要随意变动 所有网站公共元素命名及设计应以元素的应用范畴,意义为主,而非特性; 使用适用于领域内的术语/缩写’ 鼓励运用“隐喻”形象化具有共性的事务的命名!但是一定要公开发布词汇表! 1.1.1.1 基本原则 包|文件名 全部小写! 因为语言支持类的话,要进行引用,都是URL 方式的,有的系统环境认大小写!那么,包,文件使用小写。 模块文件名应该与模块中主应用函式名一致。 类名采用“Pascal格式”,即 WikiName 格式!由首字母大写单词组成!对于派生类的命名应该避免带其父类名的诱惑,一个类的名字只与它自身有关,和它的父类叫什么无关。 函数名使用小写字母开头,当函数名包含多个单词,每个子的首字母必须大写,这就是所谓的 “驼峰” 格式。我们一般鼓励使用冗长的名字,函数名应当长到足以说明函数的意图和行为。在对象中的方法,声明为 "private" 或"protected" 的, 名称的首字符必须是一个单个的下划线,这是唯一的下划线在方法名字中的用法。声明为 "public" 的从不包含下划线。 变量名和函数名一样使用小写字母开头并遵循“驼峰”格式。声明为 "private" 或 "protected" 的实例变量名必须以一个单个下划线开头,这是唯一的下划线在程序中的用法,声明为 "public" 的不应当以下划线开头。 常量命名时全部采用大写;常量一定要附有说明,特别是全局性常量!避免“魔法数字”。 全局变量名 应该有模块/文件名 作为前缀。 缩写词不要全部使用大写字母。 无论如何,当遇到以下情况,你可以用首字母大写其余字母小写来代替全部使用大写字母的方法来表示缩写词。 使用: GetHtmlStatistic. 不使用: GetHTMLStatistic. 1.1.2 大括号{}规则 在主要的大括号放置规则中,有两种是可以接受的,如下的第一种是最好的: 将大括号放置在关键词下方的同列处: if ($condition) while ($condition) { { ... ... } } 传统的UNIX的括号规则是,首括号与关键词同行,尾括号与关键字同列: if ($condition) { while ($condition) { ... ... } } 1.1.3 缩进/制表符/空格 规则 每个缩进的单位约定是一个TAB(8个空白字符宽度),需每个参与项目的开发人员在编辑器(UltraEdit、EditPlus、Zend Studio等)中进行强制设定,以防在编写代码时遗忘而造成格式上的不规范。 本缩进规范适用于PHP、JavaScript中的函数、类、逻辑结构、循环等。 4.3.2. 大括号{}、if和switch 首括号与关键词同行,尾括号与关键字同列; if结构中,if和elseif与前后两个圆括号同行,左右各一个空格,所有大括号都单独另起一行。另外,即便if后只有一行语句,仍然需要加入大括号,以保证结构清晰; switch结构中,通常当一个case块处理后,将跳过之后的case块处理,因此大多数情况下需要添加break。break的位置视程序逻辑,与case同在一行,或新起一行均可,但同一switch体中,break的位置格式应当保持一致。 以下是符合上述规范的例子: if ($condition) { switch ($var) { case 1: echo ‘var is 1'; break; case 2: echo ‘var is 2'; break; default: echo ‘var is neither 1 or 2'; break; } } else { switch ($str) { case ‘abc': $result = ‘abc'; break; default: $result = ‘unknown'; break; } } 1.1.4 条件格式 总是将恒量放在等号/不等号的左边,例如: if ( 6 == $errorNum ) ... 一个原因是假如你在等式中漏了一个等号,语法检查器会为你报错。第二个原因是你能立刻找到数值 而不是在你的表达式的末端找到它。需要一点时间来习惯这个格式,但是它确实很有用. 1.1.5 switch 格式 default case总应该存在,它应该不被到达,然而如果到达了就会触发一个错误。 如果你要创立一个变量,那就把所有的代码放在块中。 4.3.3. 运算符、小括号、空格、关键词和函数 每个运算符与两边参与运算的值或表达式中间要有一个空格,唯一的特例是字符连接运算符号两边不加空格; 左括号“(” 应和函数关键词紧贴在一起,除此以外应当使用空格将“(”同其前面内容分开; 右括号“)”除后面是“)”或者“.”以外,其他一律用空格隔开它们; 除字符串中特意需要,一般情况下,在程序以及HTML中不出现两个连续的空格; 任何情况下,PHP程序中不能出现空白的带有TAB或空格的行,即:这类空白行应当不包含任何TAB或空格。同时,任何程序行尾也不能出现多余的TAB或空格。多数编辑器具有自动去除行尾空格的功能,如果习惯养成不好,可临时使用它,避免多余空格产生; 每段较大的程序体,上、下应当加入空白行,两个程序块之间只使用1个空行,禁止使用多行。 程序块划分尽量合理,过大或者过小的分割都会影响他人对代码的阅读和理解。一般可以以较大函数定义、逻辑结构、功能结构来进行划分。少于15行的程序块,可不加上下空白行; 说明或显示部分中,内容如含有中文、数字、英文单词混杂,应当在数字或者英文单词的前后加入空格。 根据上述原则,以下举例说明正确的书写格式: $result = (($a + 1) * 3 / 2 + $num)).'Test'; $condition ? func1($var) : func2($var); $condition ? $long_statement : $another_long_statement; if ($flag) { //Statements //More than 15 lines } Showmessage(‘请使用 restore.php 工具恢复数据.’); 1.1.6 声明块的定位 声明代码块需要对齐。变量初始化的类似代码块应该列表。 例如 var $mDate var $mrDate var $mrName var $mName $mDate = 0; $mrDate = NULL; $mrName = 0; $mName = NULL; 1.1.7 记录所有的空语句 总是记录下for或者是while的空块语句,以便清楚的知道该段代码是漏掉了,还是故意不写的。 while ($dest++ = $src++) ; // VOID 1.1.8 不要采用缺省方法测试非零值 不要采用缺省值测试非零值,也就是使用: if (FALSE != f()) 比下面的方法好: if (f()) 1.1.9 引入语句 引入语句应该位于文件的头部,并在引入时说明引入文件的作用。例如: //数据库操作类 require( “db.php” ); 4.3.4. 函数定义 l 参数的名字和变量的命名规范一致; l 函数定义中的左小括号,与函数名紧挨,中间无需空格; l 开始的左大括号另起一行; l 具有默认值的参数应该位于参数列表的后面; l 函数调用与定义的时候参数与参数之间加入一个空格; l 必须仔细检查并切实杜绝函数起始缩进位置与结束缩进位置不同的现象。 例如,符合标准的定义: function authcode($string, $operation, $key = '') { //函数体 if($flag) { //Statement } } 不符合标准的定义: function authcode($string,$operation,$key = '') { //函数体 } 4.3.5. 引号 PHP中单引号和双引号具有不同的含义,最大的几项区别如下: 单引号中,任何变量($var)、特殊转义字符(如“/t /r /n”等)不会被解析,因此PHP的解析速度更快,转义字符仅仅支持“/'”和“//”这样对单引号和反斜杠本身的转义; 双引号中,变量($var)值会代入字符串中,特殊转义字符也会被解析成特定的单个字符,还有一些专门针对上述两项特性的特殊功能性转义,例如“/$”和“{$array['key']}。这样虽然程序编写更加方便,但同时PHP的解析也很慢; 数组中,如果下标不是整型,而是字符串类型,请务必用单引号将下标括起,正确的写法为$array['key'],而不是$array[key],因为不正确的写法会使PHP解析器认为key是一个常量,进而先判断常量是否存在,不存在时才以“key”作为下标带入表达式中,同时出发错误事件,产生一条Notice级错误。 因此,在绝大多数可以使用单引号的场合,禁止使用双引号。依据上述分析,可以或必须使用单引号的情况包括但不限于下述: l 字符串为固定值,不包含“/t”等特殊转义字符; l 数组的固定下标,例如$array['key']; l 表达式中不需要带入变量,例如$string = 'test';,而非$string = “test$var”; 例外的,在正则表达式(用于preg_系列函数和ereg系列函数)中,phpcms全部使用双引号,这是为了人工分析和编写的方便,并保持正则表达式的统一,减少不必要的分析混淆。 数据库SQL语句中,所有数据都不得加单引号,但是在进行sql查询之前都必须经过intval函数处理;所有字符串都必须加单引号,以避免可能的注入漏洞和SQL错误。正确的写法为: $catid = intval($catid); SELECT * FROM phpcms_member WHERE username='$_username' AND catid='$catid'; 所有数据在插入数据库之前,均需要进行addslashes()处理,以免特殊字符未经转义在插入数据库的时候出现错误。phpcms中如果已经引入了文件 common.inc.php,则所有通过 GET, POST, FILE,取得的变量默认情况下已经使用了addslashes()进行了转义,不必重复进行。如果数据处理必要(例如用于直接显示),可以使用 stripslashes() 恢复,但数据在插入数据库之前必须再次进行转义。 缓存文件中,一般对缓存数据的值采用 addcslashes($string, '/'//')进行转义。 详细出处参考:http://www.jb51.net/article/13315_2.htm 1.1.10 类的声明 类文档注释(/**……*/) 该注释中所需包含的信息,参见"文档注释" 类的声明 类实现的注释(/*……*/)如果有必要的话 该注释应包含任何有关整个类的信息,而这些信息又不适合作为类文档注释。 类的(静态)变量 首先是类的公共变量,随后是保护变量,再后是包一级别的变量(没有访问修饰符,access modifier),最后是私有变量。 实例变量 首先是公共级别的,随后是保护级别的,再后是包一级别的(没有访问修饰符),最后是私有级别的。 构造器 方法 这些方法应该按功能,而非作用域或访问权限,分组。例如,一个私有的类方法可以置于两个公有的实例方法之间。其目的是为了更便于阅读和理解代码 1.1.11 行长度 尽量避免一行的长度超过80个字符,因为很多终端和工具不能很好处理。 1.1.12 换行 当一个表达式无法容纳在一行内时,可以依据如下一般规则断开: - 在一个逗号后面断开 - 在一个操作符前面断开 - 宁可选择较高级别(higher-level)的断开,而非较低级别(lower-level)的断开 - 新的一行应该与上一行同一级别表达式的开头处对齐 - 如果以上规则导致你的代码混乱或者使你的代码都堆挤在右边,那就代之以缩进8个空格。 以下是断开方法调用的一些例子: someMethod(longExpression1, longExpression2, longExpression3,              longExpression4, longExpression5); $var = someMethod1(longExpression1,                  someMethod2(longExpression2,                               longExpression3)); 以下是两个断开算术表达式的例子。前者更好,因为断开处位于括号表达式的外边,这是个较高级别的断开。 $longName1 = $longName2 * ($longName3 + $longName4 - $longName5) + 4 * $longname6; //使用这种缩进方式 $longName1 = $longName2 * ($longName3 + $longName4                    - $longName5) + 4 * $longname6; //避免这种 以下是两个缩进方法声明的例子。前者是常规情形。后者若使用常规的缩进方式将会使第二行和第三行移得很靠右,所以代之以缩进8个空格 //传统的缩进方式 function someMethod($anArg, $anotherArg, $yetAnotherArg,           $andStillAnother) { ... } //利用8个连续空格避免过渡的缩进 function horkingLongMethodName($anArg,      $anotherArg, $yetAnotherArg,      $andStillAnother) { ... } if语句的换行通常使用8个空格的规则,因为常规缩进(4个空格)会使语句体看起来比较费劲。比如: //不要使用这种缩进方式 if ((condition1 && condition2)   || (condition3 && condition4)   ||!(condition5 && condition6)) { //错误的换行方式,没有进行缩进   doSomethingAboutIt(); //条件与此句对齐,造成阅读程序时很可能漏过此句 } //应该使用这种缩进方式 if ((condition1 && condition2)     || (condition3 && condition4)     ||!(condition5 && condition6)) {   doSomethingAboutIt(); } //或者这样的缩进方式也可以 if ((condition1 && condition2) || (condition3 && condition4)         ||!(condition5 && condition6)) {   doSomethingAboutIt(); } 这里有三种可行的方法用于处理三元运算表达式: $alpha = (aLongBooleanExpression) ? beta : gamma; $alpha = (aLongBooleanExpression) ? beta                  : gamma; $alpha = (aLongBooleanExpression)     ? beta     : gamma; 1.2目录文档 所有的目录下都需要具有README文档,其中包括: 该目录的功能及其包含内容 一个对每一文件的在线说明(带有link),每一个说明通常还应该提取文件标头的一些属性名字。 包括设置、使用说明 指导别人如何连接相关资源: 源文件索引 在线文档 纸文档 设计文档 其他对读者有帮助的东西 1.3 定义文件后缀意义 .php .asp .jsp ... 程序以及页面文件; .lbi 库文件,复用性代码块! .dwt .zpt .tmp ... 模板文件 .inc.* _inc.* 功能性程序包含文件 .txt .t2t(txt2tags结构文本) .stx(reST结构文本) 说明文档 1.4 注释约定 注释是对于那些容易忘记作用的代码添加简短的介绍性内容。请使用 C 样式的注释“/* */”和标准 C++ 注释“//”。 程序开发中难免留下一些临时代码和调试代码,此类代码必须添加注释,以免日后遗忘。所有临时性、调试性、试验性的代码,必须添加统一的注释标记“//debug”并后跟完整的注释信息,这样可以方便在程序发布和最终调试前批量检查程序中是否还存在有疑问的代码。例如: $num = 1; $flag = TRUE; //debug 这里不能确定是否需要对$flag进行赋值 if (empty($flag)) { //Statements } 1.4.1. 预定义关键字 遵从Doxygen 语法; 实际使用时,注释关键字可以由"/"或是"@"引导,并以空格,间隔具体内容! 关键字 含义 --- Structural indicators --- class 文件,类注解 page 页面 说明! par function name() param 参数名 参数说明 return 返回值(包括类型) exception 异常;例外 说明 code 代码举要! endcode --- Section indicators --- brief 摘要,表示类、属性、方法要做些什么或者什么含义。 author 作者 version { version number } note 注解;原理说明; warning 警告 attention 注意,环境需要! sa 参考 bug 已知bugs todo { paragraph describing what is to be done } 修改记录(编号规则为“#”+作者名|信箱+"::"+日期+“-”+序号) par 自定义关键词;或是大间隔空行 li 列表 test 测试方法! deprecated 禁止,不赞成 1.4.2. 模块的注释 /**_ @class file_action 文件的主要功能说明 @brief 详细描述 @author realsoft (realsoft@qq.com) @version 20060723.100 记录主要的修改点 realsoft::odbc_fetch_into()参数位置第二和第三个位置调换 @version 20020523.101 John Johnson John@crtvu.edu.cn @todo 计划中将完善的要点说明 @bug 已知欠缺要点 @test 测试说明 @par 安装 使用自定义关键词,进一步其它说明! @code 代码举要! @endcode @sa (参照) 直接使用 class 定义的文件, 可以直接自动链接去! @note 注解;原理说明; @attention 注意,环境需要! _*/ 1.4.3. 方法注释 /**_ @page _myFunc @par functin _myFunc() 使用自定义关键词作为页面标题! @brief 功能描述 Different SQL databases used different methods to combine strings together. This function provides a wrapper. @var 变量说明 @param 形参说明 @param s variable number of string parameters @param [defstr] the value to hilite @param [blank1stItem] true to leave the 1st item in list empty @param [multiple] true for listbox, false for popup @note $db->Concat($str1,$str2); @return the general type of the data: @li C for character < 200 chars @li B for BLOB (>= 200 chars) @li N for numeric floating point @li D for date @li T for timestamp @li L for logical/Boolean @li I for integer @note 注解;原理说明; @attention 注意,环境需要! @sa (参照) 直接使用 class 定义的模块名, Doxygen 可以直接自动链接去! _*/ 1.4.4. 其它说明 /**_ @page _myFile 不能使用中文 @par functin _myFunc() 使用自定义关键词作为页面标题! @brief 关键词描述 @li 列表叙述! @section sectionID 章节 @subsection subsectionID 小节 @ref subsectionID 页内跳转链接! @sa (参照) 直接使用 class 定义的文件, 可以直接自动链接去! _*/ 1.4.5. 模块、阶段性功能注释 1.4.5.1. 重要功能 或 设置 详细说明 e.g /*_******************************************** SET THE VALUE BELOW TO THE DIRECTORY WHERE THIS FILE RESIDES ADODB_RootPath has been renamed ADODB_DIR ********************************************_*/ 1.4.5.2. 自由说明 e.g // the array of types of each column (C B I L M) * 如果希望进入 Doxygen export 文档的 /// the array of types of each column (C B I L M) 1.4.5.3. 大型注释 if (0)来注释外部代码块 有时需要注释大段的测试代码,最简单的方法就是使用if (0)块: function example() { great looking code if (0) { lots of code ... .. . } more code } 你不能使用/**/,因为注释内部不能包含注释,而大段的程序中可以包含注释。 1.4.6 开头注释 所有的源文件都应该在开头有注释,其中列出类名、功能、版本信息、日期、作者和版权声明: /*  * 类名  * 功能  * 版本  * 日期  * 作者  * 版权  */ 如果对文件进行了修改,应该在文件头中说明修改目的、修改日期、修改人,并变更文件的版本信息;如果修改问文件的一部分,则在文件中进行注释即可,并且标识出修改部分的起止位置 …… /*  * 修改目的  * 修改日期  * 修改人  * 版本  */ …… 修改起始 …… …… 修改结束 …… 1.5 目录约定 动态网站开发的目录约定 建议关键功能性目录都有内容说明文档。 根目录,仅仅存放index 文件 /index.php /admin 后台管理文件夹。所有后台代码处理文件都在此文件夹 /class_inc 公用类文件夹,如:数据库连接类 /css 样式表文件夹 /images 真正使用的图片文件, 建议根据应用分目录存放, 并注意命名的规范 /fireworks FW 设计原文件, 利于大家设计共享 ,正式发布站点时会去除 /fla falsh原文件, 以及swf 动画 /library 存放复用程度高的程序组件 /download 存放供下载的工具包,比如说 falsh 的播放插件 /upload 存放客户上传的文件 /templates 模板文件夹 / templates_c 编译文件文件夹 / js javascript文件存放 / sys_log 操作日志文件夹 / index 前台页面文件夹 / language 语言包文件夹 注:文件夹目录不能超过二级。 2.防止SQL注入 2.1使用预编译语句 Prepared语句的能提高大型数据量的查询速度。免受SQL注射(injection-style)的攻击。 例如: $sql = “insert into books (one,two,three) values (?,?,?)”; $stmt=mysqli_prepare($link,$sql); $stmt->bind_param("sss",$one,$two,$three); $stmt->execute(); 2.2 对参数的传递必须进行检测和过滤,对敏感的参数应该做加密处理。 3.功能模块的规范 3.1数据库连接类 使用指定的公用的数据库连接类。同一个项目中不允许出现多个相同的数据库连接类。 例如: /class_inc/mysqli_config_class.php 注:开发大型项目应使用adodb,方便以后数据库的升级更改。 见附件 3.2 分页规范 使用指定的公用的分页类 使用一致的分页样式 例如: /class_inc/page_class.php 见附件 3.3 验证码 /class_inc/passcode.php 见附件 3.4 邮件系统 见附件 3.5 文件上传系统 见附件 3.6 用户登陆 用户登陆模块应该具有以下功能: 验证主机地址 记录登陆者的IP地址 记录登陆次数 判断验证码 文本记录登陆者的登陆信息。包括登陆的用户名,IP地址,操作系统,登陆时间等等。 字符串过滤,禁止非法字符串的输入。 敏感信息的加密,相关信息禁止明文传输 3.7 报表系统 见附件 3.8 公用函数 见附件 4. 数据库的规范 4.1 SQL语句规范 4.1.1 书写格式 1).缩进 对于存储过程文件,缩进为8个空格 对于Java source里的SQL字符串,不可有缩进,即每一行字符串不可以空格开头 2).换行 1>.Select/From/Where/Order by/Group by等子句必须另其一行写 2>.Select子句内容如果只有一项,与Select同行写 3>.Select子句内容如果多于一项,每一项单独占一行,在对应Select的基础上向右缩进8个空格 4>.From子句内容如果只有一项,与From同行写 5>.From子句内容如果多于一项,每一项单独占一行,在对应From的基础上向右缩进8个空格(Java source无缩进) 6>.Where子句的条件如果有多项,每一个条件占一行,以AND开头,且无缩进 7>.(Update)Set子句内容每一项单独占一行,无缩进 8>.Insert子句内容每个表字段单独占一行,无缩进;values每一项单独占一行,无缩进 9>.SQL文中间不允许出现空行 3).空格 2>.逗号之后必须接一个空格 3>.关键字、保留字和左括号之间必须有一个空格 4.1.2 不等于统一使用“<>”   Oracle认为"!="和"<>"是等价的,都代表不等于的意义。为了统一,不等于一律使用"<>"表示 4.1.3 使用表的别名 数据库查询,尽量使用表的别名 4.1.4 SQL语句对表字段扩展的兼容性 使用Insert时,必须指定插入的字段名,严禁不指定字段名直接插入values 4.1.5 减少子查询的使用 子查询除了可读性差之外,还在一定程度上影响了SQL运行效率 请尽量减少使用子查询的使用,用其他效率更高、可读性更好的方式替代 4.1.6 适当添加索引以提高查询效率 适当添加索引可以大幅度的提高检索速度 4.1.7 SELECT子句中避免使用'*' 当你想在SELECT子句中列出所有的COLUMN时,使用动态SQL列引用'*'是一个方便的方法,不幸的是,这是一个非常低效的方法 4.1.8 减少访问数据库的次数 4.1.9 使用DECODE函数来减少处理时间 使用DECODE函数可以避免重复扫描相同记录或重复连接相同的表      例如:   SELECT COUNT(*), SUM(SAL)   FROM EMP   WHERE DEPT_NO = '0020'   AND ENAME LIKE 'SMITH%';      SELECT COUNT(*), SUM(SAL)   FROM EMP   WHERE DEPT_NO = '0030'   AND ENAME LIKE 'SMITH%';      你可以用DECODE函数高效地得到相同结果   SELECT COUNT(DECODE(DEPT_NO, '0020', 'X', NULL)) D0020_COUNT,   COUNT(DECODE(DEPT_NO, '0030', 'X', NULL)) D0030_COUNT,   SUM(DECODE(DEPT_NO, '0020', SAL, NULL)) D0020_SAL,   SUM(DECODE(DEPT_NO, 0030, SAL, NULL)) D0030_SAL   FROM EMP   WHERE ENAME LIKE 'SMITH%';      'X'表示任何一个字段   类似的,DECODE函数也可以运用于GROUP BY和ORDER BY子句中 4.1.10 用Where子句替换HAVING子句 避免使用HAVING子句,HAVING只会在检索出所有记录之后才对结果集进行过滤,这个处理需要排序、统计等操作      如果能通过WHERE子句限制记录的数目,那就能减少这方面的开销      例如:   低效   SELECT REGION, AVG(LOG_SIZE)   FROM LOCATION   GROUP BY REGION   HAVING REGION REGION != 'SYDNEY'   AND REGION != 'PERTH'      高效   SELECT REGION, AVG(LOG_SIZE)   FROM LOCATION   WHERE REGION REGION != 'SYDNEY'   AND REGION != 'PERTH'   GROUP BY REGION 4.1.11 减少对表的查询 在含有子查询的SQL语句中,要特别注意减少对表的查询      例如:      低效   SELECT TAB_NAME   FROM TABLES   WHERE TAB_NAME = (SELECT TAB_NAME   FROM TAB_COLUMNS   WHERE VERSION = 604)   AND DB_VER = (SELECT DB_VER   FROM TAB_COLUMNS   WHERE VERSION = 604)      高效   SELECT TAB_NAME   FROM TABLES   WHERE (TAB_NAME, DB_VER) = (SELECT TAB_NAME, DB_VER   FROM TAB_COLUMNS   WHERE VERSION = 604)      Update多个Column例子:   低效   UPDATE EMP   SET EMP_CAT = (SELECT MAX(CATEGORY)   FROM EMP_CATEGORIES),   SAL_RANGE = (SELECT MAX(SAL_RANGE)   FROM EMP_CATEGORIES)   WHERE EMP_DEPT = 0020;      高效   UPDATE EMP   SET (EMP_CAT, SAL_RANGE) = (SELECT MAX(CATEGORY), MAX(SAL_RANGE)   FROM EMP_CATEGORIES)   WHERE EMP_DEPT = 0020; 4.1.12 用EXISTS替代IN 在许多基于基础表的查询中,为了满足一个条件,往往需要对另一个表进行联接      在这种情况下,使用EXISTS(或NOT EXISTS)通常将提高查询的效率      低效   SELECT *   FROM EMP (基础表)   WHERE EMPNO > 0   AND DEPTNO IN (SELECT DEPTNO   FROM DEPT   WHERE LOC = 'MELB')      高效   SELECT *   FROM EMP (基础表)   WHERE EMPNO > 0   AND EXISTS (SELECT 'X'   FROM DEPT   WHERE DEPT.DEPTNO = EMP.DEPTNO AND LOC = 'MELB') 4.1.13 用NOT EXISTS替代NOT IN 在子查询中,NOT IN子句将执行一个内部的排序和合并      无论在哪种情况下,NOT IN都是最低效的,因为它对子查询中的表执行了一个全表遍历      为了避免使用NOT IN,我们可以把它改写成外连接(Outer Joins)或NOT EXISTS      例如:   SELECT …   FROM EMP   WHERE DEPT_NO NOT IN (SELECT DEPT_NO   FROM DEPT   WHERE DEPT_CAT = 'A');      为了提高效率改写为   高效   SELECT …   FROM EMP A, DEPT B   WHERE A.DEPT_NO = B.DEPT(+)   AND B.DEPT_NO IS NULL   AND B.DEPT_CAT(+) = 'A'      最高效   SELECT …   FROM EMP E   WHERE NOT EXISTS (SELECT 'X'   FROM DEPT D   WHERE D.DEPT_NO = E.DEPT_NO   AND DEPT_CAT = 'A'); 4.1.14 用表连接替换EXISTS 通常来说,采用表连接的方式比EXISTS更有效率      例如:   SELECT ENAME   FROM EMP E   WHERE EXISTS (SELECT 'X'   FROM DEPT   WHERE DEPT_NO = E.DEPT_NO   AND DEPT_CAT = 'A');      更高效   SELECT ENAME   FROM DEPT D, EMP E   WHERE E.DEPT_NO = D.DEPT_NO   AND DEPT_CAT = 'A'; 4.1.15 用EXISTS替换DISTINCT 当提交一个包含多表信息(比如部门表和雇员表)的查询时,避免在SELECT子句中使用DISTINCT,一般可以考虑用EXIST替换      例如:      低效   SELECT DISTINCT DEPT_NO, DEPT_NAME   FROM DEPT D, EMP E   WHERE D.DEPT_NO = E.DEPT_NO      高效   SELECT DEPT_NO, DEPT_NAME   FROM DEPT D   WHERE EXISTS (SELECT 'X'   FROM EMP E   WHERE E.DEPT_NO = D.DEPT_NO);      EXISTS使查询更为迅速,因为RDBMS核心模块将在子查询的条件一旦满足后,立刻返回结果 4.1.16 用索引提高效率 4.1.18 避免在索引列上使用计算 WHERE子句中,如果索引列是函数的一部分,优化器将不使用索引而使用全表扫描      例如:      低效   SELECT …   FROM DEPT   WHERE SAL * 12 > 25000;      高效   SELECT …   FROM DEPT   WHERE SAL > 25000/12;      请务必注意,检索中不要对索引列进行处理,如:TRIM,TO_DATE,类型转换等操作,破坏索引,使用全表扫描,影响SQL执行效率 4.1.19 避免在索引列上使用IS NULL和IS NOT NULL 4.1.20 使用UNION-ALL和UNION 当SQL语句需要UNION两个查询结果集合时,这两个结果集合会以UNION-ALL的方式被合并,然后在输出最终结果前进行排序      如果用UNION ALL替代UNION,这样排序就不是必要了,效率就会因此得到提高      需要注意的是,UNION ALL将重复输出两个结果集合中相同记录,因此还是要从业务需求分析使用UNION ALL的可行性      关于索引下列经验请参考:      1).如果检索数据量超过30%的表中记录数,使用索引将没有显著的效率提高      2).在特定情况下,使用索引也许会比全表扫描慢,但这是同一个数量级上的差距;而通常情况下,使用索引比全表扫描要快几倍乃至几千倍! 4.1.21 使用PrepareStatement 4.2 数据库设计规范 4.2.1字段设计原则 a.每个表中都应该添加的3 个有用的字段。 dRecordCreationDate,在SQL Server 下默认为GETDATE() sRecordCreator,在SQL Server 下默认为NOT NULL DEFAULT USER nRecordVersion,记录的版本标记;有助于准确说明记录中出现null 数据或者丢失数据的原因时效性数据应包括“最近更新日期/时间”字段。时间标记对查找数据问题的原因、按日期重新处理/重载数据和清除旧数据特别有用。 b.表内的列[字段]的命名规则(采用前缀/后缀命名)、采用有意义的字段名 对列[字段]名应该采用标准的前缀和后缀。如键是数字类型:用 _N 后缀;字符类型:_C 后缀;日期类型:_D 后缀。 c.选择数字类型和文本类型的长度应尽量充足 假设客户ID 为10 位数长。那你应该把数据库表字段的长度设为12 或者13 个字符长。但这额外占据的空间却无需将来重构整个数据库就可以实现数据库规模的增长了。 d.增加删除标记字段 在表中包含一个“删除标记”字段,这样就可以把行标记为删除。在关系数据库里不要单独删除某一行;最好采用清除数据程序而且要仔细维护索引整体性。 e.提防大小写混用的对象名和特殊字符 采用全部大写而且包含下划符的名字具有更好的可读性(CUSTOMER_DATA),绝对不要在对象名的字符之间留空格。 f.小心保留词 要保证你的字段名没有和保留词、数据库系统或者常用访问方法冲突,比如,用 DESC 作为说明字段名。后果可想而知,DESC 是 DESCENDING 缩写后的保留词。表里的一个 SELECT * 语句倒是能用,但得到的却是一大堆毫无用处的信息。 g.保持字段名和类型的一致性 在命名字段并为其指定数据类型的时候一定要保证一致性。假如字段在表1中叫做“agreement_number”,就别在表2里把名字改成“ref1”。假如数据类型在表1里是整数,那在表2里可就别变成字符型了。当然在表1(ABC)有处键ID,则为了可读性,在表2做关联时可以命名为ABC_ID。 h.避免使用触发器 触发器的功能通常可以用其他方式实现。在调试程序时触发器可能成为干扰。假如你确实需要采用触发器,你最好集中对它文档化。 4.2.2 索引使用原则 索引是从数据库中获取数据的最高效方式之一。95%的数据库性能问题都可以采用索引技术得到解决。 1) 逻辑主键使用唯一的成组索引,对系统键(作为存储过程)采用唯一的非成组索引,对任何外键列采用非成组索引。考虑数据库的空间有多大,表如何进行访问,还有这些访问是否主要用作读写。 2) 大多数数据库都索引自动创建的主键字段,但是可别忘了索引外键,它们也是经常使用的键,比如运行查询显示主表和所有关联表的某条记录就用得上。 3) 不要索引memo/note 字段,不要索引大型字段(有很多字符),这样作会让索引占用太多的存储空间。 4) 不要索引常用的小型表 不要为小型数据表设置任何键,假如它们经常有插入和删除操作就更别这样作了。对这些插入和删除操作的索引维护可能比扫描表空间消耗更多的时间。 4.2.3 数据库命名规范 a. 实体(表)的命名 1) 表以名词或名词短语命名,确定表名是采用复数还是单数形式,此外给表的别名定义简单规则(比方说,如果表名是一个单词,别名就取单词的前4 个字母;如果表名是两个单词,就各取两个单词的前两个字母组成4 个字母长的别名;如果表的名字由3 个单词组成,从头两个单词中各取一个然后从最后一个单词中再取出两个字母,结果还是组成4 字母长的别名,其余依次类推) 对工作用表来说,表名可以加上前缀WORK_ 后面附上采用该表的应用程序的名字。在命名过程当中,根据语义拼凑缩写即可。注意,由于ORCLE会将字段名称统一成大写或者小写中的一种,所以要求加上下划线。 举例: 定义的缩写 Sales: Sal 销售; Order: Ord 订单; Detail: Dtl 明细; 则销售订单明细表命名为:Sal_Ord_Dtl; 2) 如果表或者是字段的名称仅有一个单词,那么建议不使用缩写,而是用完整的单词。 举例: 定义的缩写 Material Ma 物品; 物品表名为:Material, 而不是 Ma. 是字段物品编码则是:Ma_ID;而不是Material_ID 3) 所有的存储值列表的表前面加上前缀Z 目的是将这些值列表类排序在数据库最后。 4) 所有的冗余类的命名(主要是累计表)前面加上前缀X 冗余类是为了提高数据库效率,非规范化数据库的时候加入的字段或者表 5) 关联类通过用下划线连接两个基本类之后,再加前缀R的方式命名,后面按照字母顺序罗列两个表名或者表名的缩写。 关联表用于保存多对多关系。 如果被关联的表名大于10个字母,必须将原来的表名的进行缩写。如果没有其他原因,建议都使用缩写。 举例:表Object与自身存在多对多的关系,则保存多对多关系的表命名为:R_Object; 表 Depart和Employee;存在多对多的关系;则关联表命名为R_Dept_Emp b. 属性(列)的命名 1) 采用有意义的列名,表内的列要针对键采用一整套设计规则。每一个表都将有一个自动ID作为主健,逻辑上的主健作为第一组候选主健来定义,如果是数据库自动生成的编码,统一命名为:ID;如果是自定义的逻辑上的编码则用缩写加“ID”的方法命名。如果键是数字类型,你可以用_NO 作为后缀;如果是字符类型则可以采用_CODE 后缀。对列名应该采用标准的前缀和后缀。 举例:销售订单的编号字段命名:Sal_Ord_ID;如果还存在一个数据库生成的自动编号,则命名为:ID。 2) 所有的属性加上有关类型的后缀,注意,如果还需要其它的后缀,都放在类型后缀之前。 注: 数据类型是文本的字段,类型后缀TX可以不写。有些类型比较明显的字段,可以不写类型后缀。 3) 采用前缀命名 给每个表的列名都采用统一的前缀,那么在编写SQL表达式的时候会得到大大的简化。这样做也确实有缺点,比如破坏了自动表连接工具的作用,后者把公共列名同某些数据库联系起来。 c. 视图的命名 1) 视图以V作为前缀,其他命名规则和表的命名类似; 2) 命名应尽量体现各视图的功能。 d. 触发器的命名 触发器以TR作为前缀,触发器名为相应的表名加上后缀,Insert触发器加"_I",Delete触发器加"_D",Update触发器加"_U",如:   TR_Customer_I,TR_Customer_D,TR_Customer_U。 e. 存储过程名 存储过程应以"UP_"开头,和系统的存储过程区分,后续部分主要以动宾形式构成,并用下划线分割各个组成部分。如增加代理商的帐户的存储过程为"UP_Ins_Agent_Account"。 f. 变量名 变量名采用小写,若属于词组形式,用下划线分隔每个单词,如@my_err_no。 g. 命名中其他注意事项 1) 以上命名都不得超过30个字符的系统限制。变量名的长度限制为29(不包括标识字符@)。 2) 数据对象、变量的命名都采用英文字符,禁止使用中文命名。绝对不要在对象名的字符之间留空格。 3) 小心保留词,要保证你的字段名没有和保留词、数据库系统或者常用访问方法冲突。 4) 保持字段名和类型的一致性,在命名字段并为其指定数据类型的时候一定要保证一致性。假如数据类型在一个表里是整数,那在另一个表里可就别变成字符型了。 4.4.命名原则 命名是程序规划的核心。古人相信只要知道一个人真正的名字就会获得凌驾于那个人之上的不可思议的力量。只要你给事物想到正确的名字,就会给你以及后来的人带来比代码更强的力量。 名字就是事物在它所处的生态环境中一个长久而深远的结果。总的来说,只有了解系统的程序员才能为系统取出最合适的名字。如果所有的命名都与其自然相适合,则关系清晰,含义可以推导得出,一般人的推想也能在意料之中。 就一般约定而言,类、函数和变量的名字应该总是能够描述让代码阅读者能够容易的知道这些代码的作用。形式越简单、越有规则,就越容易让人感知和理解。应该避免使用模棱两可,晦涩不标准的命名。 4.4.1. 变量、对象、函数名 变量、对象、函数名一律为小写格式,除非必要,单词之间一般不使用下划线“_”进行分割; 以标准计算机英文为蓝本,杜绝一切拼音、或拼音英文混杂的命名方式; 变量命名只能使用项目中有据可查的英文缩写方式,例如可以使用$data而不可使用$data1、$data2这样容易产生混淆的形式,应当使用$articledata、$userdata这样一目了然容易理解的形式; 可以合理的对过长的命名进行缩写,例如$bio($biography),$tpp($threadsPerPage),前提是英文中有这样既有的缩写形式,或字母符合英文缩写规范; 必须清楚所使用英文单词的词性,在权限相关的范围内,大多使用$enable***、$is*** 、的形式,前者后面接动词,后者后面接形容词。 4.4.2. 常量 常量应该总是全部使用大写字母命名,少数特别必要的情况下,可使用划线来分隔单词; PHP 的内建值 TRUE、FALSE 和NULL必须全部采用大写字母书写。 4.5.变量的初始化与逻辑检查 任何变量在进行累加、直接显示或存储前必需进行初使化,例如: $number = 0; //数值型初始化 $string = ‘'; //字符串初始化 $array = array(); //数组初始化 判断一个无法确定(不知道是否已被赋值)的变量时,可用empty()或isset(),而不要直接使用if($switch)的形式,除非你确切的知道此变量一定已经被初始化并赋值。 empty()和isset()的区别为: l bool empty(mixed var) n 如果 var 是非空或非零的值,则 empty() 返回 FALSE。换句话说,""、0、"0"、NULL、FALSE、array()、var $var; 以及没有任何属性的对象都将被认为是空的,如果 var 为空,则返回 TRUE。 l bool isset(mixed var[, mixed var[, ...]]) n 如果 var 存在则返回 TRUE,否则返回 FALSE。 n 如果已经使用 unset() 释放了一个变量之后,它将不再是 isset()。若使用 isset() 测试一个被设置成 NULL 的变量,将返回 FALSE。同时要注意的是一个 NULL 字节("/0")并不等同于 PHP 的 NULL 常数。 判断一个变量是否为数组,请使用is_array(),这种判断尤其适用于对数组进行遍历的操作,例如foreach(),因为如果不事先判断,foreach()会对非数组类型的变量报错; 判断一个数组元素是否存在,可使用isset($array[‘key']),也可使用empty(),两者异同见上。 4.6.安全性 PHP中的变量不并不像C语言那样需要事先声明,解释器会在第一次使用时自动创建他们,同样类型也不需要指定,解释器会根据上下文环境自动确定。从开发人员的角度来看,这无疑是一种极其方便的处理方法。一个变量被创建了,就可以在程序中的任何地方使用。这导致的结果就是开发人员工经常不注意初始化变量。因此,为了提高程序的安全性,我们不能相信任何没有明确定义的变量。所有的变量在定义使用前要初使化以防止恶意构造提交的变量覆盖程序中使用的变量。 细节可以阅读(http://www.securereality.com.au/studyinscarlet.txt)这篇文档,该文档里罗列了PHP常见的安全问题,阅读该文档是非常有必要的! 4.7.兼容性 代码设计应当兼顾PHP 高低版本的特性,当前,应仍然以PHP 4.3.0作为最低通过平台,尽量不使用高版本PHP 新增的函数、常数或者常量。如果使用只在高版本才具备的函数,必须对其进行二次封装,自动判断当前PHP版本,并自行编写低版本下的兼容代码; 对于个别函数,参数要求或者代码要求应当以较为严格的PHP版本为准; 除非必要,不要使用PHP扩展模块中的函数。使用时应当加入必要的判断,当服务器环境不支持此函数的时候,进行必要的处理。文档和程序中的功能说明中,也应加上兼容性说明。 4.8.代码重用 代码的有效重用可以减少效率的损失与资源的浪费。在开发软件项目时为了避免重复劳动和浪费时间。开发人员应尽量提高现有代码的重用率,同时将更多的精力用在新技术的应用和新功能的创新开发上面。 l 在需要多次使用代码,并且对于您希望实现的任务没有可用的内置 PHP 函数时,不吝啬定义函数或类。开发者须根据功能、调用情况,将函数放置于include目录并以.func.php作为函数文件后缀,将类放置于include/class目录。超过3行,实现相同功能的程序切勿在不同程序中多次出现,这是无法容忍和回避的问题; l 在任何时候都不要出现同一个程序中出现两段或更多的相似代码或相同代码,即便在不同程序中,也应尽力避免。开发者应当总是有能力找到避免代码大段(超过10行)重复或类似的情况。 需要强调的是,本部分虽然篇幅较短,但却是十分需要经验,并将花费开发者大量时间和精力去进行优化的部分,任何产品开发者必须时刻清楚和理解代码重用的重要性和必要性,切实在增强产品效率、逻辑性和可读性上下功夫,这是一名优秀软件开发者所必须具备的基本素质。 4.9.其他细节问题 4.9.1. 包含调用 包含调用程序文件,请全部使用require_once,以避免可能的重复包含问题; 包含调用缓存文件,由于缓存文件无法保证100%正确打开,请使用include_once或include。在必要时,可以使用@include_once或@include的方式,以忽略错误提示; 包含和调用代码中,须以PHPCMS_ROOT.'/'开头,应避免直接写程序文件名(例如:require_once ‘x.php';)的做法; 所有被包含和调用的程序文件,包括但不限于程序、缓存或模板,通常其不能被直接URL请求。phpcms通过在./include/common.inc.php中定义一个标记性常量IN_PHPCMS,来判断程序是否被合法调用。因此,在除了./include/common.inc.php以外的任何一个被包含和调用的程序文件中,需要包含以下内容,以使得访问者无法直接通过URL请求该文件: defined('IN_PHPCMS') or exit('Access Denied'); 4.9.2. 错误报告级别 在软件开发和调试阶段,请使用error_reporting(E_ALL);作为默认的错误报告级别,此级别最为严格,能够报告程序中所有的错误、警告和提示信息,以帮助开发者检查和核对代码,避免大多数安全性问题和逻辑错误、拼写错误。error_reporting()可以在./include/common.inc.php的头几行进行设置。 在软件发布时,请使用error_reporting(E_ERROR | E_WARNING | E_PARSE);作为默认的错误报告级别,以利于用户使用并将无谓错误提示信息降至最低。 5. 数据库设计 5.1.字段 5.1.1. 表和字段命名 表和字段的命名以前面《4.4命名原则》的约定为基本准则。 所有数据表名称,只要其名称是可数名词,则必须以复数方式命名,例如:phpcms_member(用户表);存储多项内容的字段,或代表数量的字段,也应当以复数方式命名,例如:hits(查看次数)、items(内容数量)。 当几个表间的字段有关连时,要注意表与表之间关联字段命名的统一,如phpcms_article_1表中的articleid与phpcms_article_data_1表中的articleid。 代表id自增量的字段,通常用以下几种形式: l 一般情况下,使用全称的形式,例如userid、articleid; l 没有功能性作用,只为管理和维护方便而设的id,可以使用全称的形式,也可只将其命名为id。 篇幅所限,无法一一赘述,但所有与表、字段相关的命名,请务必大量参考phpcms现有字段的命名方式,以保证命名的系统性和统一性。 5.1.2. 字段结构 允许NULL值的字段,数据库在进行比较操作时,会先判断其是否为NULL,非NULL时才进行值的必对。因此基于效率的考虑,所有字段均不能为空,即全部NOT NULL; 预计不会存储非负数的字段,例如各项id、发帖数等,必须设置为UNSIGNED类型。UNSIGNED类型比非UNSIGNED类型所能存储的正整数范围大一倍,因此能获得更大的数值存储空间; 存储开关、选项数据的字段,通常使用tinyint(1)非UNSIGNED类型,少数情况也可能使用enum()结果集的方式。tinyint作为开关字段时,通常1为打开;0为关闭;-1为特殊数据,例如N/A(不可用);高于1的为特殊结果或开关二进制数组合(详见phpcms中相关代码); MEMORY/HEAP类型的表中,要尤其注意规划节约使用存储空间,这将节约更多内存。例如cdb_sessions表中,就将IP地址的存储拆分为4个tinyint(3) UNSIGNED类型的字段,而没有采用char(15)的方式; 任何类型的数据表,字段空间应当本着足够用,不浪费的原则,数值类型的字段取值范围见下表: 字段类型 存储空间(b) UNSIGNED 取值范围 tinyint 1 否 -128~127 是 0~255 smallint 2 否 -32768~32767 是 0~65535 mediumint 3 否 -8388608~8388607 是 0~16777215 int 4 否 -2147483648~2147483647 是 0~4294967295 bigint 8 否 -9223372036854775808 ~9223372036854775807 是 0 ~18446744073709551615 5.2.SQL语句 所有SQL语句中,除了表名、字段名称以外,全部语句和函数均需大写,应当杜绝小写方式或大小写混杂的写法。例如select * from phpcms_member;是不符合规范的写法。 很长的SQL语句应当有适当的断行,依据JOIN、FROM、ORDER BY等关键字进行界定。 通常情况下,在对多表进行操作时,要根据不同表名称,对每个表指定一个1~2个字母的缩写,以利于语句简洁和可读性。 如下的语句范例,是符合规范的: $result = $db->query(”SELECT m.*, i.* FROM “.TABLE_MEMBER.” m, “.TABLE_MEMBERINFO.” i WHERE m.userid=i.userid AND m.userid='$_userid'); 5.3.性能与效率 5.3.1. 定长与变长表 包含任何varchar、text等变长字段的数据表,即为变长表,反之则为定长表。 l 对于变长表,由于记录大小不同,在其上进行许多删除和更改将会使表中的碎片更多。需要定期运行OPTIMIZE TABLE以保持性能。而定长表就没有这个问题; l 如果表中有可变长的字段,将它们转换为定长字段能够改进性能,因为定长记录易于处理。但在试图这样做之前,应该考虑下列问题: l 使用定长列涉及某种折衷。它们更快,但占用的空间更多。char(n) 类型列的每个值总要占用n 个字节(即使空串也是如此),因为在表中存储时,值的长度不够将在右边补空格; l 而varchar(n)类型的列所占空间较少,因为只给它们分配存储每个值所需要的空间,每个值再加一个字节用于记录其长度。因此,如果在char和varchar类型之间进行选择,需要对时间与空间作出折衷; l 变长表到定长表的转换,不能只转换一个可变长字段,必须对它们全部进行转换。而且必须使用一个ALTER TABLE语句同时全部转换,否则转换将不起作用; l 有时不能使用定长类型,即使想这样做也不行。例如对于比255字符更长的串,没有定长类型; l 在设计表结构时如果能够使用定长数据类型尽量用定长的,因为定长表的查询、检索、更新速度都很快。必要时可以把部分关键的、承担频繁访问的表拆分,例如定长数据一个表,非定长数据一个表。例如phpcms的phpcms_member表等。因此规划数据结构时需要进行全局考虑; 进行表结构设计时,应当做到恰到好处,反复推敲,从而实现最优的数据存储体系。 5.3.2. 运算与检索 数值运算一般比字符串运算更快。例如比较运算,可在单一运算中对数进行比较。而串运算涉及几个逐字节的比较,如果串更长的话,这种比较还要多。 如果串列的值数目有限,应该利用普通整型或emum类型来获得数值运算的优越性。 更小的字段类型永远比更大的字段类型处理要快得多。对于字符串,其处理时间与串长度直接相关。一般情况下,较小的表处理更快。对于定长表,应该选择最小的类型,只要能存储所需范围的值即可。例如,如果mediumint够用,就不要选择bigint。对于可变长类型,也仍然能够节省空间。一个TEXT 类型的值用2 字节记录值的长度,而一个LONGTEXT 则用4字节记录其值的长度。如果存储的值长度永远不会超过64KB,使用TEXT 将使每个值节省2字节。 5.3.3. 结构优化与索引优化 索引能加快查询速度,而索引优化和查询优化是相辅相成的,既可以依据查询对索引进行优化,也可以依据现有索引对查询进行优化,这取决于修改查询或索引,哪个对现有产品架构和效率的影响最小。 索引优化与查询优化是多年经验积累的结晶,在此无法详述,但仍然给出几条最基本的准则。 首先,根据产品的实际运行和被访问情况,找出哪些SQL语句是最常被执行的。最常被执行和最常出现在程序中是完全不同的概念。最常被执行的SQL语句,又可被划分为对大表(数据条目多的)和对小表(数据条目少的)的操作。无论大表或小表,有可分为读(SELECT)多、写(UPDATE/INSERT)多或读写都多的操作。 对常被执行的SQL语句而言,对大表操作需要尤其注意: l 写操作多的,通常可使用写入缓存的方法,先将需要写或需要更新的数据缓存至文件或其他表,定期对大表进行批量写操作。同时,应尽量使得常被读写的大表为定长类型,即便原本的结构中大表并非定长。大表定长化,可以通过改变数据存储结构和数据读取方式,将一个大表拆成一个读写多的定长表,和一个读多写少的变长表来实现; l 读操作多的,需要依据SQL查询频率设置专门针对高频SQL语句的索引和联合索引。 而小表就相对简单,加入符合查询要求的特定索引,通常效果比较明显。同时,定长化小表也有益于效率和负载能力的提高。字段比较少的小定长表,甚至可以不需要索引。 其次,看SQL语句的条件和排序字段是否动态性很高(即根据不同功能开关或属性,SQL查询条件和排序字段的变化很大的情况),动态性过高的SQL语句是无法通过索引进行优化的。惟一的办法只有将数据缓存起来,定期更新,适用于结果对实效性要求不高的场合。 MySQL索引,常用的有PRIMARY KEY、INDEX、UNIQUE几种,详情请查阅MySQL文档。通常,在单表数据值不重复的情况下,PRIMARY KEY和UNIQUE索引比INDEX更快,请酌情使用。 事实上,索引是将条件查询、排序的读操作资源消耗,分布到了写操作中,索引越多,耗费磁盘空间越大,写操作越慢。因此,索引决不能盲目添加。对字段索引与否,最根本的出发点,依次仍然是SQL语句执行的概率、表的大小和写操作的频繁程度。 5.3.4. 查询优化 MySQL中并没有提供针对查询条件的优化功能,因此需要开发者在程序中对查询条件的先后顺序人工进行优化。例如如下的SQL语句: SELECT * FROM table WHERE a>'0' AND b<'1' ORDER BY c LIMIT 10; 事实上无论a>'0'还是b<'1'哪个条件在前,得到的结果都是一样的,但查询速度就大不相同,尤其在对大表进行操作时。 开发者需要牢记这个原则:最先出现的条件,一定是过滤和排除掉更多结果的条件;第二出现的次之;以此类推。因而,表中不同字段的值的分布,对查询速度有着很大影响。而ORDER BY中的条件,只与索引有关,与条件顺序无关。 除了条件顺序优化以外,针对固定或相对固定的SQL查询语句,还可以通过对索引结构进行优化,进而实现相当高的查询速度。原则是:在大多数情况下,根据WHERE条件的先后顺序和ORDER BY的排序字段的先后顺序而建立的联合索引,就是与这条SQL语句匹配的最优索引结构。尽管,事实的产品中不能只考虑一条SQL语句,也不能不考虑空间占用而建立太多的索引。 同样以上面的SQL语句为例,最优的当table表的记录达到百万甚至千万级后,可以明显的看到索引优化带来的速度提升。 依据上面条件优化和索引优化的两个原则,当table表的值为如下方案时,可以得出最优的条件顺序方案: 字段a 字段b 字段c 1 7 11 2 8 10 3 9 13 -1 0 12 最优条件:b<'1' AND a>'0' 最优索引:INDEX abc (b, a, c) 原因:b<'1'作为第一条件可以先过滤掉75%的结果。如果以a>'0'作为第一条件,则只能先过滤掉25%的结果 注意1:字段c由于未出现于条件中,故条件顺序优化与其无关 注意2:最优索引由最优条件顺序得来,而非由例子中的SQL语句得来 注意3:索引并非修改数据存储的物理顺序,而是通过对应特定偏移量的物理数据而实现的虚拟指针 EXPLAIN语句是检测索引和查询能否良好匹配的简便方法。在phpMyAdmin或其他MySQL客户端中运行EXPLAIN+查询语句,例如EXPLAIN select * FROM table WHERE a>'0' AND b<'1' ORDER BY c;这种形式,即使得开发者无需模拟上百万条数据,也可以验证索引是否合理,相关细节请参考MySQL说明。 值得提出的是,Using filesort是最不应当出现的情况,如果EXPLAIN得出此结果,说明数据库为这个查询专门建立了一个用以缓存结果的临时表文件,并在查询结束后删除。众所周知,硬盘I/O速度始终是计算机存储的瓶颈,因此,查询中应当尽全力避免高执行频率的SQL语句使用filesort。尽管,开发者永远都不可能保证产品中的全部SQL语句都不会使用filesort。 限于篇幅,本文档远远没有涵盖数据库优化的方方面面,例如:联合索引与普通索引的可重用性、JOIN连接的索引设计、MEMORY/HEAP表等。数据库优化实际上就是在很多因素和利弊间不断权衡、修改,惟有在成功与失败经验中反复推敲才能得出的经验,这种经验往往就是最难能可贵和价值连城的。 5.3.5. 兼容性问题 由于MySQL 3.23至5.0的变化很大,因此程序中尽量不使用特殊的SQL语句,以免带来兼容性问题,并给数据库移植造成困难。 通常在MySQL 4.1以上版本,phpcms应使用相当的字符集来存储,例如GBK/BIG5/UTF-8。传统的latin1编码虽然有一定的兼容性,但仍然不是推荐的选择。使用相应非默认字符集时,程序每次运行时需要使用SET NAMES ‘character_set';来规定连接、传输和结果的字符集。 Mysql 5.0以上新增了数种SQL_MODE,默认的SQL_MODE依服务器安装设置不同而不同,因此程序每次运行时需要使用SET SQL_MODE='';来规定当前的SQL模式。 6. 模板设计 6.1.代码标记 HTML代码标记一律采用小写字母形式,杜绝任何使用大写字母的方式 模板中所有的逻辑体,如{if}、{loop}等,必须前后使用HTML注释(

),即类似

的形式。事实上,phpcms模板编译器是支持不加HTML注释的逻辑体写法的,但加入注释可以使得模板可读性更好,同时方便用户使用DreamWeaver或FrontPage等对模板进行修改。 6.2.书写规则 6.2.1. HTML 所有HTML标记参数赋值需使用双引号包含,例如,应当使用 而绝对不能使用 。 在任何情况下,产品中的模板文件必须采用手写HTML代码的方式,而绝对不能使用DreamWeaver、FrontPage等自动网页制作工具进行撰写或修改。 6.2.2. 变量 模板中使用的变量,依据作用和出现位置不同,分为几种方式: 逻辑体中,即被包围起来的部分,例如这种形式,其中的变量书写规范与PHP程序中完全一致; 开发者需要使用{}将变量括起来,以免出现模板编译错误,可能的情况如下: 变量前后含有中括号的或其他敏感字符的(包括但不限于“$”、“'”等),正确的写法为descriptionnew[{$buddy[buddyid]}]; 数组的下标为变量的,正确的写法为{$extcredits[$creditstrans][title]}; 其他变量十分复杂的情况。 6.2.3. 语言元素 6.2.4. 缩进 在phpcms的*.html模板文件中,由于具备逻辑结构,故不考虑任何HTML本身的缩进,所有缩进均意为着逻辑上的缩进结构。缩进采用TAB方式,不使用空格作为缩进符号,仅需适当断行即可。例如:

{$article[‘title']}

7. 文件与目录 7.1.文件命名 所有包含PHP代码的程序文件或半程序文件,应以小写.php作为扩展名,而不要使用.phtml、.php3、.inc、.class等作为扩展名。 普通程序 能够被URL直接调用的程序,例如list.php、index.php,直接使用程序名+.php的方式命名 函数库和类库程序 分别以小写.func.php和.class.php作为扩展名。函数库和类库程序只能被其他程序引用,而不能独立运行。其中不能包含任何流程性的、不属于任何函数或类的程序代码。 流程性程序 以小写.inc.php作为扩展名。只能被其他程序引用,而不能独立运行。其中不能包含任何函数或类代码的程序代码。 模板源文件 以小写.html作为扩展名。模板源文件按照phpcms模板编码规则进行编写,不是可以执行的程序,而只能被phpcms模板编译器所解析,放置于./templates/default或./templates下的其他模板目录下。 模板目标文件 模板文件被编译后自动生成的目标程序,以小写. php作为扩展名,存放于./data/templates目录下。 语言包文件 以小写.lang.php作为扩展名,只能存放模板或程序使用的语言包信息。 缓存文件 此类文件为系统自动生成,以cache_xxx.php、usergroup_xxx.php、style_xxx.php等类似形式命名,存放于./data/cache目录下。 7.2.目录命名 phpcms目录命名以前面《4.4命名原则》的约定为基本准则。在可能的情况下,多以复数形式出现,如./templates、./images等。 由于目录数量较少,因此目录命名大多是一些习惯和约定俗成,开发人员如需新建目录,应与项目组成员进行磋商,达成一致后方可实施。 7.3.空目录索引 请在所有不包含普通程序(即能够被URL直接调用的程序)的目录中放置一个1字节的index.htm文件,内容为一个空格。几乎除phpcms根目录以外,所有目录都属于这一类型,因此开发者需要在这些目录全部放入空index.htm文件,以避免当http服务器的Directory Listing打开时,服务器文件被索引和列表。 附件目录等敏感目录,要在程序中实现相应功能,当新建下级目录时,必须自动写入一个空的index.htm文件,以避免新建目录被索引的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值