1.2 Discuz! 的编码规范 第2部分

本篇涵盖:性能与效率 ~ 空目录索引

性能与效率

定长与变长表

包含任何 varchar、text 等变长字段的数据表,即为变长表,反之则为定长表。

  • 对于变长表,由于记录大小不同,在其上进行许多删除和更改将会使表中的碎片更多。需要定期运行 OPTIMIZE TABLE 以保持性能。而定长表就没有这个问题;

  • 如果表中有可变长的字段,将它们转换为定长字段能够改进性能,因为定长记录易于处理。但在试图这样做之前,应该考虑下列问题:

  • 使用定长列涉及某种折衷。它们更快,但占用的空间更多。char(n) 类型列的每个值总要占用n 个字节(即使空串也是如此),因为在表中存储时,值的长度不够将在右边补空格;

  • 而varchar(n)类型的列所占空间较少,因为只给它们分配存储每个值所需要的空间,每个值再加一个字节用于记录其长度。因此,如果在char和varchar类型之间进行选择,需要对时间与空间作出折衷;

  • 变长表到定长表的转换,不能只转换一个可变长字段,必须对它们全部进行转换。而且必须使用一个ALTER TABLE语句同时全部转换,否则转换将不起作用;

  • 有时不能使用定长类型,即使想这样做也不行。例如对于比255字符更长的串,没有定长类型;

  • 在设计表结构时如果能够使用定长数据类型尽量用定长的,因为定长表的查询、检索、更新速度都很快。必要时可以把部分关键的、承担频繁访问的表拆分,例如定长数据一个表,非定长数据一个表。例如Discuz!的cdb_members和cdb_memberfields、cdb_forums和cdb_forumfields表等。因此规划数据结构时需要进行全局考虑;

进行表结构设计时,应当做到恰到好处,反复推敲,从而实现最优的数据存储体系。

运算与检索

  • 数值运算一般比字符串运算更快。例如比较运算,可在单一运算中对数进行比较。而串运算涉及几个逐字节的比较,如果串更长的话,这种比较还要多。

  • 如果串列的值数目有限,应该利用普通整型或emum类型来获得数值运算的优越性。

  • 更小的字段类型永远比更大的字段类型处理要快得多。对于字符串,其处理时间与串长度直接相关。一般情况下,较小的表处理更快。对于定长表,应该选择最小的类型,只要能存储所需范围的值即可。例如,如果mediumint够用,就不要选择bigint。对于可变长类型,也仍然能够节省空间。一个 TEXT 类型的值用2 字节记录值的长度,而一个 LONGTEXT 则用4字节记录其值的长度。如果存储的值长度永远不会超过64KB,使用 TEXT 将使每个值节省2字节。

结构优化与索引优化

索引能加快查询速度,而索引优化和查询优化是相辅相成的,既可以依据查询对索引进行优化,也可以依据现有索引对查询进行优化,这取决于修改查询或索引,哪个对现有产品架构和效率的影响最小。 索引优化与查询优化是多年经验积累的结晶,在此无法详述,但仍然给出几条最基本的准则。 首先,根据产品的实际运行和被访问情况,找出哪些SQL语句是最常被执行的。最常被执行和最常出现在程序中是完全不同的概念。最常被执行的SQL语句,又可被划分为对大表(数据条目多的)和对小表(数据条目少的)的操作。无论大表或小表,又可分为读(SELECT)多、写(UPDATE/INSERT)多或读写都多的操作。 对常被执行的SQL语句而言,对大表操作需要尤其注意:

  • 写操作多的,通常可使用写入缓存的方法,先将需要写或需要更新的数据缓存至文件或其他表,定期对大表进行批量写操作,例如Discuz!中点击数延迟更新机制,就是依据此原理实现。同时,应尽量使得常被读写的大表为定长类型,即便原本的结构中大表并非定长。大表定长化,可以通过改变数据存储结构和数据读取方式,将一个大表拆成一个读写多的定长表,和一个读多写少的变长表来实现;

  • 读操作多的,需要依据SQL查询频率设置专门针对高频SQL语句的索引和联合索引。

而小表就相对简单,加入符合查询要求的特定索引,通常效果比较明显。同时,定长化小表也有益于效率和负载能力的提高。字段比较少的小定长表,甚至可以不需要索引。

其次,看SQL语句的条件和排序字段是否动态性很高(即根据不同功能开关或属性,SQL查询条件和排序字段的变化很大的情况),动态性过高的SQL语句是无法通过索引进行优化的。惟一的办法只有将数据缓存起来,定期更新,适用于结果对实效性要求不高的场合。 MySQL索引,常用的有PRIMARY KEY、INDEX、UNIQUE几种,详情请查阅MySQL文档。通常,在单表数据值不重复的情况下,PRIMARY KEY和UNIQUE索引比INDEX更快,请酌情使用。

事实上,索引是将条件查询、排序的读操作资源消耗,分布到了写操作中,索引越多,耗费磁盘空间越大,写操作越慢。因此,索引决不能盲目添加。对字段索引与否,最根本的出发点,依次仍然是SQL语句执行的概率、表的大小和写操作的频繁程度。

查询优化

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
最优条件:b<'1' AND a>'0' 最优索引:INDEX abc (b, a, c) 原因:b<'1' 作为第一条件可以先过滤掉75%的结果。如果以 a>'0' 作为第一条件,则只能先过滤掉25%的结果

注意:

  • 字段c由于未出现于条件中,故条件顺序优化与其无关

  • 最优索引由最优条件顺序得来,而非由例子中的SQL语句得来

  • 索引并非修改数据存储的物理顺序,而是通过对应特定偏移量的物理数据而实现的虚拟指针

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表等。数据库优化实际上就是在很多因素和利弊间不断权衡、修改,惟有在成功与失败经验中反复推敲才能得出的经验,这种经验往往就是最难能可贵和价值连城的。

兼容性问题

  • 由于MySQL 3.23至5.0的变化很大,因此程序中尽量不使用特殊的SQL语句,以免带来兼容性问题,并给数据库移植造成困难。

  • 通常在MySQL 4.1以上版本,Discuz!应使用相当的字符集来存储,例如GBK/BIG5/UTF-8。传统的latin1编码虽然有一定的兼容性,但仍然不是推荐的选择。使用相应非默认字符集时,程序每次运行时需要使用 SET NAMES 'character_set'; 来规定连接、传输和结果的字符集。

  • Mysql 5.0以上新增了数种SQL_MODE,默认的SQL_MODE依服务器安装设置不同而不同,因此程序每次运行时需要使用SET SQL_MODE='';来规定当前的SQL模式。

模板设计

代码标记

HTML代码标记一律采用小写字母形式,杜绝任何使用大写字母的方式 模板中所有的逻辑体,如{if}、{loop}等,必须前后使用HTML注释(),即类似的形式。事实上,Discuz!模板编译器是支持不加HTML注释的逻辑体写法的,但加入注释可以使得模板可读性更好,同时方便用户使用DreamWeaver或VSCODE等对模板进行修改。 在HTML标记中使用的逻辑体无需使用HTML注释(),即:&lt;input type="text"{if xxx} value="1"{/if} />

书写规则

HTML

所有HTML标记参数赋值需使用双引号包含,例如,应当使用&lt;input type="text" name="test" value="ok" />,而绝对不能使用&lt;input type=text name=test value=ok />。 在任何情况下,产品中的模板文件必须采用手写HTML代码的方式,而绝对不能使用DreamWeaver、VSCODE等自动网页制作工具进行撰写或修改。 非成对标记必须以"/>"结尾,如: &lt;input …/>,&lt;input /> 标记的属性必须按照以下顺序书写:&lt;input type="" name="" id="" class="" … />

变量

模板中使用的变量,依据作用和出现位置不同,分为几种方式: - 逻辑体中,即被包围起来的部分,例如这种形式,其中的变量书写规范与PHP程序中完全一致;

  • 模板内容中,即未被包围起来的部分,与PHP程序中最大的不同是,对数组字符串类型下标无需加入单引号,因为Discuz!模板编译器会在对模板进行编译时将这部分的单引号自动加入。

偶尔的,开发者需要使用{}将变量括起来,以免出现模板编译错误,可能的情况如下:

  • 变量前后含有中括号的或其他敏感字符的(包括但不限于"$"、"'"等),正确的写法为$descriptionnew[{$buddy['buddyid']}];;

  • 数组的下标为变量的,正确的写法为{$extcredits[$creditstrans]['title']};

  • 其他变量十分复杂的情况。

语言元素

Discuz!模板编译器可以解析对应于./templates/default/templates.lang.php(以默认模板为例)的语言元素。在模板中调用相应语言元素的代码为{lang item},其中item为在templates.lang.php中定义的语言元素名。

缩进

在Discuz!的模板文件中,由于具备逻辑结构,故不考虑任何HTML本身的缩进,所有缩进均意为着逻辑上的缩进结构。缩进采用TAB方式,不使用空格作为缩进符号,仅需适当断行即可。例如:

`{loop $threadlist $thread}`
    &lt;table cellspacing="0" cellspadding="0" border="0"&gt;
    &lt;tr&gt;&lt;td&gt;$thread[message]&lt;/tr&gt;&lt;/td&gt;
    &lt;/table&gt;
`{/loop}`

文件与目录

文件命名

所有包含PHP代码的程序文件或半程序文件,应以小写.php作为扩展名,而不要使用.phtml、.php3、.inc、.class等作为扩展名。

普通程序 能够被URL直接调用的程序,例如home.php、index.php、forum.php,直接使用程序名+.php的方式命名

函数库和类库程序 分别以小写function_xxxx.php和class_xxxx.php的格式命名书写。函数库和类库程序只能被其他程序引用,而不能独立运行。其中不能包含任何流程性的、不属于任何函数或类的程序代码。

流程性程序 以小写.inc.php作为扩展名。只能被其他程序引用,而不能独立运行。其中不能包含任何函数或类代码的程序代码。

模板源文件 以小写作为扩展名。模板源文件按照Discuz!模板编码规则进行编写,不是可以执行的程序,而只能被Discuz!模板编译器所解析,放置于./templates/default或./templates下的其他模板目录下。

模板目标文件 模板文件被编译后自动生成的目标程序,以小写.tpl.php作为扩展名,存放于./data/template目录下。

语言包文件

  • 以小写lang_xxxx.php格式命名,只能存放模板或程序使用的语言包信息。

  • 模板语言包存放在对应模块名下的lang_template.php,例如:家园的模板语言包source\language\home\lang_template.php

缓存文件 此类文件为系统自动生成,以cache_xxx.php、usergroup_xxx.php、style_xxx.php等类似形式命名,存放于./data/cache目录下。

目录命名

  • Discuz!目录命名以前面《4.4命名原则》的约定为基本准则。在可能的情况下,多以复数形式出现,如 ./templates、./images 等。

  • 由于目录数量较少,因此目录命名大多是一些习惯和约定俗成,开发人员如需新建目录,应与项目组成员进行磋商,达成一致后方可实施。

附:程序目录索引

空目录索引

  • 请在所有不包含普通程序(即能够被URL直接调用的程序)的目录中放置一个1字节的index.htm文件,内容为一个空格。几乎除Discuz!根目录以外,所有目录都属于这一类型,因此开发者需要在这些目录全部放入空index.htm文件,以避免当http服务器的目录列表打开时,服务器文件被索引和列表。

  • 附件目录等敏感目录,要在程序中实现相应功能,当新建下级目录时,必须自动写入一个空的index.htm文件,以避免新建目录被索引的问题。

更新时间:2012-5-3


整理自 Discuz! 官方开放平台技术文档(open.dismall.com),由 WorkBuddy 整理发布。