MYSQL索引

原文

概述

用来加快查询的技术很多,其中最重要的是索引。通常索引能够快速提高查询速度。如果不适用索引,MYSQL必须从第一条记录开始然后读完整个表直到找出相关的行。表越大,花费的时间越多。但也不全是这样。本文讨论索引是什么以及如何使用索引来改善性能,以及索引可能降低性能的情况。

索引的本质

MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。提取句子主干,就可以得到索引的本质:索引是数据结构。

数据库查询是数据库的最主要功能之一。我们都希望查询数据的速度能尽可能的快,因此数据库系统的设计者会从查询算法的角度进行优化。最基本的查询算法当然是顺序查找(linear search),这种复杂度为O(n)的算法在数据量很大时显然是糟糕的,好在计算机科学的发展提供了很多更优秀的查找算法,例如二分查找(binary search)、二叉树查找(binary tree search)等。如果稍微分析一下会发现,每种查找算法都只能应用于特定的数据结构之上,例如二分查找要求被检索数据有序,而二叉树查找只能应用于二叉查找树上,但是数据本身的组织结构不可能完全满足各种数据结构(例如,理论上不可能同时将两列都按顺序进行组织),所以,在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。

索引的存储分类

索引是在MYSQL的存储引擎层中实现的,而不是在服务层实现的。所以每种存储引擎的索引都不一定完全相同,也不是所有的存储引擎都支持所有的索引类型。MYSQL目前提供了一下4种索引。

  • B-Tree 索引:最常见的索引类型,大部分引擎都支持B树索引。
  • HASH 索引:只有Memory引擎支持,使用场景简单。
  • R-Tree 索引(空间索引):空间索引是MyISAM的一种特殊索引类型,主要用于地理空间数据类型。
  • Full-text (全文索引):全文索引也是MyISAM的一种特殊索引类型,主要用于全文索引,InnoDB从MYSQL5.6版本提供对全文索引的支持。

Mysql目前不支持函数索引,但是能对列的前面某一部分进行索引,例如标题title字段,可以只取title的前10个字符进行索引,这个特性可以大大缩小索引文件的大小,但前缀索引也有缺点,在排序Order By和分组Group By 操作的时候无法使用。用户在设计表结构的时候也可以对文本列根据此特性进行灵活设计。
语法:create index idx_title on film (title(10))

MyISAM、InnoDB引擎、Memory三个常用引擎类型比较

索引 MyISAM引擎 InnoDB引擎 Memory引擎
B-Tree 索引 支持 支持 支持
HASH 索引 不支持 不支持 支持
R-Tree 索引 支持 不支持 不支持
Full-text 索引 不支持 暂不支持 不支持

B-TREE索引类型

  • 普通索引
    这是最基本的索引类型,而且它没有唯一性之类的限制。普通索引可以通过以下几种方式创建:
    (1)创建索引: CREATE INDEX 索引名 ON 表名(列名1,列名2,…);
    (2)修改表: ALTER TABLE 表名ADD INDEX 索引名 (列名1,列名2,…);
    (3)创建表时指定索引:CREATE TABLE 表名 ( […], INDEX 索引名 (列名1,列名 2,…) );
  • UNIQUE索引
    表示唯一的,不允许重复的索引,如果该字段信息保证不会重复例如身份证号用作索引时,可设置为unique:
    (1)创建索引:CREATE UNIQUE INDEX 索引名 ON 表名(列的列表);
    (2)修改表:ALTER TABLE 表名ADD UNIQUE 索引名 (列的列表);
    (3)创建表时指定索引:CREATE TABLE 表名( […], UNIQUE 索引名 (列的列表) );
  • 主键:PRIMARY KEY索引
    主键是一种唯一性索引,但它必须指定为“PRIMARY KEY”。
    (1)主键一般在创建表的时候指定:“CREATE TABLE 表名( […], PRIMARY KEY (列的列表) ); ”。
    (2)但是,我们也可以通过修改表的方式加入主键:“ALTER TABLE 表名ADD PRIMARY KEY (列的列表); ”。
    每个表只能有一个主键。 (主键相当于聚合索引,是查找最快的索引)
    注:不能用CREATE INDEX语句创建PRIMARY KEY索引

索引的设置语法

一 设置索引

在执行CREATE TABLE语句时可以创建索引,也可以单独用CREATE INDEX或ALTER TABLE来为表增加索引。

1.ALTER TABLE – ALTER TABLE用来创建普通索引UNIQUE索引PRIMARY KEY索引

  • ALTER TABLE table_name ADD INDEX index_name (column_list)
  • ALTER TABLE table_name ADD UNIQUE (column_list)
  • ALTER TABLE table_name ADD PRIMARY KEY (column_list)

2.CREATE INDEX – CREATE INDEX可对表增加普通索引或UNIQUE索引。

  • CREATE INDEX index_name ON table_name (column_list)
  • CREATE UNIQUE INDEX index_name ON table_name (column_list)

二 删除索引

可利用ALTER TABLE或DROP INDEX语句来删除索引。类似于CREATE INDEX语句,DROP INDEX可以在ALTER TABLE内部作为一条语句处理,语法如下。

  • DROP INDEX index_name ON talbe_name
  • ALTER TABLE table_name DROP INDEX index_name
  • ALTER TABLE table_name DROP PRIMARY KEY

其中,前两条语句是等价的,删除掉table_name中的索引index_name。
第3条语句只在删除PRIMARY KEY索引时使用,因为一个表只可能有一个PRIMARY KEY索引,因此不需要指定索引名。如果没有创建PRIMARY KEY索引,但表具有一个或多个UNIQUE索引,则MySQL将删除第一个UNIQUE索引。

如果从表中删除了某列,则索引会受到影响。对于多列组合的索引,如果删除其中的某列,则该列也会从索引中删除。如果删除组成索引的所有列,则整个索引将被删除。

三 查看索引

  • Table:表的名称
  • Non_unique:如果索引不能包括重复词,则为0。如果可以,则为1
  • Key_name:索引的名称
  • Seq_in_index:索引中的列序列号,从1开始
  • Column_name:列名称
  • Collation:列以什么方式存储在索引中。在MySQL中,有值‘A’(升序)或NULL(无分类)。
  • Cardinality:索引中唯一值的数目的估计值。通过运行ANALYZE TABLE或myisamchk -a可以更新。基数根据被存储为整数的统计数据来计数,所以即使对于小型表,该值也没有必要是精确的。基数越大,当进行联合时,MySQL使用该索引的机会就越大。
  • Sub_part:如果列只是被部分地编入索引,则为被编入索引的字符的数目。如果整列被编入索引,则为NULL。
  • Packed:指示关键字如何被压缩。如果没有被压缩,则为NULL。
  • Null:如果列含有NULL,则含有YES。如果没有,则该列含有NO。
  • Index_type:用过的索引方法(BTREE, FULLTEXT, HASH, RTREE)。
  • Comment:更多评注。

索引选择性

一 索引选择原则

1. 较频繁的作为查询条件的字段应该创建索引
2. 唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件
3. 更新非常频繁的字段不适合创建索引

当然,并不是存在更新的字段就适合创建索引,从判定策略的用语上也可以看出,是”非常频繁”的字段。到底什么样的更新频率应该算是”非常频繁”呢?每秒?每分钟?还是每小时呢?说实话,还真难定义。很多时候是通过比较同一时间段内被更新的次数和利用该字段作为条件的查询次数来判断的,如果通过该字段的查询并不是很多,可能几个小时或是更长才会执行一次,更新反而比查询更频繁,那这样的字段肯定不适合创建索引。反之,如果我们通过该字段的查询比较频繁,但更新并不是特别多,比如查询几十次或更多才可能会产生一次更新,那我个人觉得更新所带来的附加成本也是可以接受的。

4. 不会出现在 WHERE 子句中的字段不该创建索引

二 索引选择原则细述

  • 性能优化过程中,选择在哪个列上创建索引是最非常重要的。可以考虑使用索引的主要有 两种类型的列:在where子句中出现的列在join子句中出现的列,而不是在SELECT关键字后选择列表的列;
  • 索引列的基数越大,索引的效果越好。例如,存放出生日期的列具有不同的值,很容易区分行,而用来记录性别的列,只有”M”和”F”,则对此进行索引没有多大用处,因此不管搜索哪个值,都会得出大约一半的行,(  见索引选择性注意事项对选择性解释;)
  • 使用短索引,如果对字符串列进行索引,应该指定一个前缀长度,可节省大量索引空间,提升查询速度;

    例如,有一个CHAR(200)列,如果在前10个或20个字符内,多数值是唯一的,那么就不要对整个列进行索引。对前10个或者20个字符进行索引能够节省大量索引空间,也可能会使查询更快。较小的索引涉及的磁盘IO较少,较短的值比较起来更快。更为重要的是,对于较短的键值,所以高速缓存中的快能容纳更多的键值,因此,MYSQL也可以在内存中容纳更多的值。这样就增加了找到行而不用读取索引中较多快的可能性。

  • 利用最左前缀

三 索引选择注意事项

既然索引可以加快查询速度,那么是不是只要是查询语句需要,就建上索引?答案是否定的。因为索引虽然加快了查询速度,但索引也是有代价的:索引文件本身要消耗存储空间,同时索引会加重插入、删除和修改记录时的负担,另外,MySQL在运行时也要消耗资源维护索引,因此索引并不是越多越好。

一般两种情况下不建议建索引:

  1. 表记录比较少,例如一两千条甚至只有几百条记录的表,没必要建索引,让查询做全表扫描就好了;

    至于多少条记录才算多,这个个人有个人的看法,我个人的经验是以2000作为分界线,记录数不超过 2000可以考虑不建索引,超过2000条可以酌情考虑索引。

  2. 索引的选择性较低。所谓索引的选择性(Selectivity),是指不重复的索引值(也叫基数,Cardinality)与表记录数(#T)的比值:

    显然选择性的取值范围为(0, 1],选择性越高的索引价值越大,这是由B+Tree的性质决定的。例如,上文用到的employees.titles表,如果title字段经常被单独查询,是否需要建索引,我们看一下它的选择性:

    title的选择性不足0.0001(精确值为0.00001579),所以实在没有什么必要为其单独建索引。
  3. MySQL只对一下操作符才使用索引: <,<=,=,>,>=,between,in, 以及某些时候的 like(不以通配符%_开头的情形)
  4. 不要过度索引,只保持所需的索引。每个额外的索引都要占用额外的磁盘空间,并降低写操作的性能。 在修改表的内容时,索引必须进行更新,有时可能需要重构,因此,索引越多,所花的时间越长。

四 索引的弊端

索引的益处已经清楚了,但是我们不能只看到这些益处,并认为索引是解决查询优化的圣经,只要发现 查询运行不够快就将 WHERE 子句中的条件全部放在索引中。

确实,索引能够极大地提高数据检索效率,也能够改善排序分组操作的性能,但有不能忽略的一个问题就是索引是完全独立于基础数据之外的一部分数据。假设在Table ta 中的Column ca 创建了索引 idx_ta_ca,那么任何更新 Column ca 的操作,MySQL在更新表中 Column ca的同时,都须要更新Column ca 的索引数据,调整因为更新带来键值变化的索引信息。而如果没有对 Column ca 进行索引,MySQL要做的仅仅是更新表中 Column ca 的信息。这样,最明显的资源消耗就是增加了更新所带来的 IO 量和调整索引所致的计算量。此外,Column ca 的索引idx_ta_ca须要占用存储空间,而且随着 Table ta 数据量的增加,idx_ta_ca 所占用的空间也会不断增加,所以索引还会带来存储空间资源消耗的增加。

MySQL数据类型笔记

在MySQL中,选择正确的数据类型,对于性能至关重要。一般应该遵循下面两步:(1)确定合适的大类型:数字字符串时间二进制;(2)确定具体的类型:有无符号取值范围变长定长等。
在数据类型设置方面,尽量用更小的数据类型,因为它们通常有更好的性能,花费更少的硬件资源。并且,尽量把字段定义为 NOT NULL,避免使用 NULL

1 数值类型

1.1 说明

类型 大小 范围(有符号) 范围(无符号) 用途
TINYINT           1 字节 (-128, 127) (0, 255) 小整数值
SMALLINT           2 字节 (-32 768, 32 767) (0, 65 535) 大整数值
MEDIUMINT           3 字节 (-8 388 608, 8 388 607) (0, 16 777 215) 大整数值
INT或INTEGER           4 字节 (-2 147 483 648, 2 147 483 647) (0, 4 294 967 295) 大整数值
BIGINT           8 字节 (-9 233 372 036 854 775 808, 9 223 372 036 854 775 807) (0, 18 446 744 073 709 551 615) 极大整数值
FLOAT           4 字节 (-3.402 823 466 E+38, 1.175 494 351 E-38),0,(1.175 494 351 E-38,3.402 823 466 351 E+38) 0, (1.175 494 351 E-38, 3.402 823 466 E+38) 单精度
浮点数值
DOUBLE           8 字节 (1.797 693 134 862 315 7 E+308, 2.225 073 858 507 201 4 E-308), 0, (2.225 073 858 507 201 4 E-308, 1.797 693 134 862 315 7 E+308) 0, (2.225 073 858 507 201 4 E-308, 1.797 693 134 862 315 7 E+308) 双精度
浮点数值
DECIMAL 对DECIMAL(M,D) ,如果M>D,为M+2否则为D+2 依赖于M和D的值 依赖于M和D的值 小数值

1.2 优化建议

  1. 如果整形数据没有负数,如ID号,建议指定为 UNSIGNED无符号类型,容量可以扩大一倍。
  2. 建议使用 TINYINT代替 ENUMBITENUMSET
  3. 避免使用整数的显示宽度,也就是说,不要用 INT(10)类似的方法指定字段显示宽度,直接用 INT。关于INT显示宽度
  4. INT UNSIGNED来存储IPv4地址,用 VARBINARY来存储IPv6地址,当然存储之前需要用PHP函数转换。
  5. DECIMAL最适合保存准确度要求高,而且用于计算的数据,比如价格。但是在使用 DECIMAL类型的时候,注意长度设置。
  6. 建议使用整形类型来运算和存储实数,方法是,实数乘以相应的倍数后再操作。
  7. 整数通常是最佳的数据类型,因为它速度快,并且能使用 AUTO_INCREMENT

2 日期和时间

2.1 说明

类型 大小
(字节)
范围 格式 用途
DATE    3 1000-01-01 到 9999-12-31 YYYY-MM-DD 日期值
TIME    3 ‘-838:59:59’ 到 ‘838:59:59’ HH:MM:SS 时间值或持续时间
YEAR    1 1901 到 2155 YYYY 年份值
DATETIME    8 1000-01-01 00:00:00 到 9999-12-31 23:59:59 YYYY-MM-DD HH:MM:SS 混合日期和时间值
TIMESTAMP    8 1970-01-01 00:00:00 到 2037 年某时 YYYYMMDDhhmmss 混合日期和时间值,时间戳

2.2 优化建议

  1. MySQL能存储的最小时间粒度为秒。
  2. 建议用 DATE数据类型来保存日期。MySQL中默认的日期格式是 yyyy-mm-dd
  3. 用MySQL的内建类型 DATETIMEDATETIME来存储时间,而不是使用字符串。
  4. 当数据格式为 TIMESTAMPDATETIME时,可以用 CURRENT_TIMESTAMP作为默认(MySQL5.6以后),MySQL会自动返回记录插入的确切时间。
  5. TIMESTAMP是UTC时间戳,与时区相关。
  6. DATETIME的存储格式是一个YYYYMMDD HH:MM:SS的整数,与时区无关,你存了什么,读出来就是什么。
  7. 除非有特殊需求,否则建议使用 TIMESTAMP,它比 DATETIME更节约空间。
  8. 有时人们把Unix的时间戳保存为整数值,但是这通常没有任何好处,这种格式处理起来不太方便,我们并不推荐它。

3 字符串

3.1 说明

类型 大小 用途
CHAR 0-255字节 定长字符串
VARCHAR 0-65535 字节 变长字符串
TINYBLOB 0-255字节 不超过 255 个字符的二进制字符串
TINYTEXT 0-255字节 短文本字符串
BLOB 0-65 535字节 二进制形式的长文本数据
TEXT 0-65 535字节 长文本数据
MEDIUMBLOB 0-16 777 215字节 二进制形式的中等长度文本数据
MEDIUMTEXT 0-16 777 215字节 中等长度文本数据
LONGBLOB 0-4 294 967 295字节 二进制形式的极大文本数据
LONGTEXT 0-4 294 967 295字节 极大文本数据

3.2 优化建议

  1. 字符串的长度相差较大用 VARCHAR;字符串短,且所有值都接近一个长度用 CHAR
  2. CHARVARCHAR适用于包括人名、邮政编码、电话号码和不超过255个字符长度的任意字母数字组合。那些要用来计算的数字不要用 VARCHAR类型保存,因为可能会导致一些与计算相关的问题。换句话说,可能影响到计算的准确性和完整性。
  3. BINARYVARBINARY存储的是二进制字符串,与字符集无关。
  4. BLOB系列存储二进制字符串,与字符集无关。 TEXT系列存储非二进制字符串,与字符集相关。一般情况下,你可以认为 BLOB是一个更大的 VARBINARYTEXT是一个更大的 VARCHAR
  5. BLOBTEXT都不能有默认值。

4 INT显示宽度

我们经常会使用命令来创建数据表,而且同时会指定一个长度,如下。但是,这里的长度并非是 TINYINT类型存储的最大长度,而是显示的最大长度。

这里表示user表的id字段的类型是 TINYINT,可以存储的最大数值是 255。所以,

在存储数据时,如果存入值小于等于 255,如 200,虽然超过2位,但是没有超出TINYINT类型长度,所以可以正常保存;如果存入值大于 255,如 500,那么MySQL会自动保存为 TINYINT类型的最大值 255

在查询数据时,不管查询结果为何值,都按实际输出。这里TINYINT(2)中 2的作用就是,当需要在查询结果前填充 时,命令中加上 ZEROFILL就可以实现,如:

查询结果如果是 5,那输出就是 05。如果指定 TINYINT(5),那输出就是 00005,其实实际存储的值还是 5,而且存储的数据不会超过 255,只是MySQL输出数据时在前面填充了

在MySQL命令中,字段的类型长度 TINYINT(2)INT(11)不会影响数据的插入,只会在使用 ZEROFILL时有用,让查询结果前填充

提高MySQL性能的7个技巧

原文:7 keys to better MySQL performance
作者:Peter Zaitsev
译者:Peter

译者注: 随着尺寸和负载的增长,MySQL的性能会趋于下降。记住这些诀窍,便可保持MySQL的流畅运行。


id
测量应用程序的方法之一是看性能。而性能的指标之一便是用户体验,通俗的说法就是“用户是否需要等待更长的时间才能得到他们想要的东西”。

这个指标在不同的应用场合而有所改变。对于移动购物应用,响应时间不能超过几秒钟。对于员工的人力资源页面,可能需要多花几秒钟的时间。

有很多关于性能如何影响用户行为的研究:

无论采用何种标准,都必须保持良好的应用性能。否则,用户会抱怨(或者更糟的是,转到不同的应用程序)。影响应用程序性能的因素之一是数据库性能。应用程序、网站和数据库之间的交互对于建立应用程序性能的好坏至关重要。

这种交互的一个核心组件是应用程序如何查询数据库以及数据库如何响应请求。无论如何,MySQL都是最受欢迎的数据库管理系统之一。在生产环境中,越来越多的企业正在转向使用MySQL(和其他开源数据库)作为数据库解决方案。

有许多配置MySQL的方法可以帮助确保数据库对查询作出快速响应,并使应用程序性能降低到最低限度。

以下是帮助优化MySQL数据库性能的一些基本技巧。

优化技巧 #1:学习如何使用 EXPLAIN

使用任何数据库所做的两个最重要的决定是设计应用程序实体之间的关系如何映射到表(数据库模式),以及设计应用程序如何以所需的格式获得所需的数据(查询)。

复杂的应用程序可以有复杂的模式和查询。如果想得到应用程序所需要的性能和扩展性,不能仅仅依靠直觉来理解如何执行查询。

应该学习如何使用EXPLAIN命令,而不是随意的猜测和想象。此命令展示了如何执行查询,并让您了解所期望的性能,以及查询将如何随着数据大小的变化而伸缩。

有许多工具–比如MySQLWorkbench–可以可视化EXPLAIN输出,但仍然需要理解基础知识才能理解它。

EXPLAIN命令提供输出的有两种不同的格式:老式的表格式和更现代的结构化JSON文档,它提供了更多的细节(如下所示):

[/crayon]

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

应该查看的一个组件是“query cost”。query cost是指MySQL根据查询执行的总开销来考虑这个特定查询的代价,并且基于许多不同的因素。

简单查询的查询开销通常小于1,000。开销在1,000到100,000之间的查询被认为是中等开销的查询,而且如果每秒只运行数百个这样的查询(而不是数万个),通常会比较快。

开销超过100,000的查询可以当作是昂贵的。通常,当您是系统上的单个用户时,这些查询仍会快速运行,但您应该仔细考虑在交互式应用程序中使用此类查询的频率(尤其是随着用户数量的增长)。

当然,这些数字只是性能的一个大概的体现,但它们展示了一般原则。您的系统可能更好地处理查询工作负载,也可能更糟,这取决于其体系结构和配置。

决定查询开销的主要因素是查询是否正确使用索引。EXPLAIN 命令可以告诉您查询是否使用索引(通常是因为索引是如何在数据库中创建的,或者查询本身是如何设计的)。这就是为什么学会使用 EXPLAIN 是如此重要。

优化技巧 #2:创建正确的索引

索引通过减少查询必须扫描的数据库中的数据量来提高查询效率。MySQL中的索引用于加速数据库中的访问,并帮助执行数据库约束(如 UNIQUE和FOREIGN KEY )。

数据库索引很像图书索引。它们被保存在自己的位置,并且包含主数据库中已经存在的信息。它们是指向数据所在位置的参考方法或映射。索引不会更改数据库中的任何数据。它们只是指向数据的位置。

没有完全适用于任何工作负载的索引。而应该始终在系统运行的查询上下文中查看索引。

索引良好的数据库不仅运行得更快,而且即使缺少一个索引也会使数据库慢如蜗牛。使用EXPLAIN(如前所述)查找缺少的索引并添加它们。但是要小心:不要添加你不需要的索引!不必要的索引会降低数据库的速度
(请查看关于MySQL索引最佳实践的介绍)。

优化技巧 #3:拒绝使用默认设置

与任何软件一样,MySQL有许多可配置的设置,可用于修改行为(以及最终的性能)。与任何软件一样,管理员忽略了许多这些可配置的设置,最终在默认模式下使用。

要从MySQL中获得最佳性能,了解可配置的的MySQL设置是非常重要的,更重要的是将它们设置为最适合您的数据库环境。

默认情况下,MySQL用于小规模的开发安装,而不是生产规模。您通常希望配置MySQL以使用所有可用的内存资源,并允许应用程序需要的连接数量。

下面是三个MySQL性能优化设置,您应该始终仔细检查:

innodb_ buffer_ pool_size:缓冲池用于存放缓存数据和索引。这是使用具有大容量RAM的系统作为数据库服务器的主要原因。如果只运行InnoDB存储引擎,通常会将80%的内存分配给缓冲池。如果您正在运行非常复杂的查询,或者有大量的并发数据库连接,或大量的表,可能需要将此值降低一个档次,以便为其他操作分配更多的内存。

在设置InnoDB缓冲池大小时,需要确保不要设置得太大,否则会导致交换。这绝对会影响数据库性能。一种简单的检查方法是查看Percona Monitoring and Management中的系统概述图中的交换活动:


id 
如图所示,有时进行一些交换是可以的。但是,如果看到持续每秒1MB或更多的交换活动,则需要减少缓冲池大小(或其他内存使用)。

如果在第一次访问时没有正确地获得innodb_ Buffer_ pool_ size的值,不用担心。从MySQL5.7开始,便可以动态更改InnoDB缓冲池的大小,而无需重新启动数据库服务器。

innodb_ log_ file_ size:这是单个InnoDB日志文件的大小。默认情况下,InnoDB使用两个值,这样您就可以将这个数字加倍,从而获得InnoDB用于确保事务持久的循环重做日志空间的大小。这也优化了将更改应用到数据库。设置innodb_ log_ file_ size是一个权衡的问题。分配的重做空间越大,对于写密集型工作负载而言,性能就越好,但是如果系统断电或出现其他问题,崩溃恢复的时间就越长。

如何知道MySQL的性能是否受到当前InnoDB日志文件大小的限制?可以通过查看实际使用了多少可用的重做日志空间来判断。最简单的方法是查看Percona Monitor and Management InnoDB Metrics仪表板。在下图中,InnoDB日志文件的大小不够大,因为使用的空间非常接近可用的重做日志空间(由红线表示)。日志文件的大小应该至少比保持系统最佳运行所用的空间大20%。


id 
MAX_ Connections:大型应用程序连接数通常需高于默认值。不同于其它变量,如果没有正确设置它,就不会有性能问题(本身)。相反,如果连接的数量不足以满足您的应用程序的需要,那么您的应用程序将无法连接到数据库(在您的用户看来,这就像是停机时间)。所以正确处理这个变量很重要。

如果在多个服务器上运行多个组件的复杂应用程序,很难知道需要多少连接。幸运的是,MySQL可以很容易地看到在峰值操作时使用了多少连接。通常,您希望确保应用程序使用的最大连接数与可用的最大连接数之间至少有30%的差距。查看这些数字的一种简单方法是在Percona监控和管理的MySQL概述仪表板中使用MySQL连接图。下图显示了一个健全的系统,其中有大量的附加连接可用。


id 
需要记住的一点是,如果数据库运行缓慢,应用程序通常会创建过多的连接。在这种情况下,您应该处理数据库的性能问题,而不是简单地允许更多的连接。更多的连接会使底层的性能问题变得更糟。

(注意:当将max_Connections变量设置为明显高于默认值时,通常需要考虑增加其他参数,如表缓存的大小和打开的MySQL文件的数量。但是,这不属于本文讨论的范畴。)

优化技巧 #4:将数据库保存在内存中

近年来,我们看到了向固态磁盘(SSD)的过渡。尽管SSD比旋转硬盘快得多,但它们仍然无法与RAM中的数据相比。这种差异不仅来自存储性能本身,还来自数据库在从磁盘或SSD存储中检索数据时必须做的额外工作。

随着最新硬件的改进,无论是在云端运行还是管理自己的硬件,都越来越有可能将数据库存储在内存中。

更好的消息是,您不需要将所有数据库都放入内存中,就可以获得内存中的大部分性能优势。您只需将工作数据(最频繁访问的数据)集存入内存中。

你可能已经看到一些文章提供了一些具体的数字,说明应该将数据库的哪个部分保存在内存中,从10%到33%不等。事实上,没有“一刀切”的数字。适合内存的最佳性能优势的数据量与工作负载相关。与其寻找一个特定的“万能”数字,不如检查一下数据库在其稳定状态下运行的I/O(通常在启动后几个小时)。看看READ,因为如果数据库在内存中,则可以完全消除READ。写总是需要发生的,不管你有多少内存可用。

下面,您可以在Percona监控和管理的InnoDBMetrics仪表板中的 InnoDB I/O图中看到 I/O。


 
在上面的图表中,您可以看到高达每秒2,000个I/O操作的峰值,这表明(至少对于工作负载的某些部分)数据库工作集不适合内存。

优化技巧 #5:使用SSD存储

如果您的数据库不适合内存(即使不适合),您仍然需要快速存储来处理写操作,并在数据库升温时(重新启动后)避免性能问题。如今,SSD即是快速存储的代名词。

出于成本或可靠性的原因,一些“专家”仍然主张使用旋转磁盘(机械磁盘)。坦率地说,当涉及到操作数据库时,这些论点往往已经过时或完全错误。今天,SSD以较高的价格提供着可观的性能和可靠性。

然而,并非所有SSD都是适用的。对于数据库服务器,您应该使用为服务器工作负载设计的SSD,这种SSD会对数据起到保护作用(例如,在断电期间)。避免使用为台式计算机和笔记本电脑设计的商用SSD。

通过NVMe或Intel OpTan技术连接的SSD可提供最佳性能。即使作为SAN、NAS或cloud block设备远程连接,与旋转磁盘相比,SSD仍然具有更优越的性能。

优化技巧 #6:横向扩展

即使是高性能的服务器也有其局限性。有两种扩展方式:up和out。纵向扩展意味着购买更多的硬件。这可能很昂贵,而且硬件很快就会过时。横向扩展以处理更多的负载有几个好处:

      1.可以利用较小且成本较低的系统。
      2.通过横向扩展,进行线性扩展更快更容易。
      3.因为数据库分布在多台物理机器上,所以数据库不会受到单个硬件故障点的影响。

虽然横向扩展是有好处的,但也有一定的局限性。扩展需要复制,例如基本的MySQL复制或Percona XtraDB Cluster,以实现数据同步。但是作为回报,可以获得额外的性能和高可用性。如果您需要更大的扩展,请使用MySQL分片。

您还需要确保连接到集群体系结构的应用程序能够找到所需的数据–通常通过一些代理服务器和负载平衡器(如ProxySQLHAProxy)。

在计划横向扩展时,避免过早地扩展。使用分布式数据库往往更复杂。现代硬件和MySQL服务器只使用一台服务器就可以得到良好的体验。最近发布的MySQL 8候选版本表明,它能够在单个系统上处理200多万个简单查询。

优化技巧 #7:可观测性

设计最好的系统时要考虑到可观察性-MySQL也不例外.。

一旦您启动、运行并正确调整了MySQL环境,就不能仅仅设置而不进行管理。数据库环境会受到系统或工作负载更改的影响。准备好应对诸如流量高峰、应用程序错误和MySQL故障等意外。这些事情能够而且将会发生。

当发生问题时,你需要迅速而有效地解决它们。这样做的唯一方法是设置某种监视解决方案并对其进行适当的初始化。这使您能够在数据库环境在生产中运行时看到它正在发生的情况,并在出现问题时分析服务器数据。理想情况下,系统允许您在问题发生之前或在问题发展到用户可以看到其影响之前进行预防。

监控工具有诸如MySQL Enterprise Monitor、Monyog和 Percona Monitoring and Management (PMM),后者具有免费和开源的额外优势。这些工具为监视和故障排除提供了很好的可操作性。

随着越来越多的公司转向开源数据库(特别是MySQL),以便在大规模生产环境中管理和服务其业务数据,他们将需要集中精力保持这些数据库的优化和最佳运行效率。与所有对您的业务目标至关重要的事情一样,您的数据库性能可能会导致或破坏你的业务目标或成果。MySQL是一个可以为应用程序和网站提供优质的数据库解决方案,但需要进行调整以满足您的需要,并进行监视以发现和防止瓶颈和性能问题。

PeterZaitsev是Percona的联合创始人和首席执行官,Percona时企业级MySQL和MongoDB解决方案和服务的提供商。由O‘Reilly出版的《High Performance MySQL》是最受欢迎的MySQL性能书籍之一。Zaitsev经常在PerconaDatabasePerformanceBlog.com上发表博客,并在世界各地的会议中发言。

php126个常用的正则表达式

基于php常用正则表达式的整理汇总
1        /\w+([-+.’]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/  电子邮件
2        “^\d+$”  //非负整数(正整数 + 0)
3        “^[0-9]*[1-9][0-9]*$”  //正整数
4        “^((-\d+)|(0+))$”  //非正整数(负整数 + 0)
5        “^-[0-9]*[1-9][0-9]*$”  //负整数
6        “^-?\d+$”    //整数
7        “^\d+(\.\d+)?$”  //非负浮点数(正浮点数 + 0)
8        “^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$”  //正浮点数
9        “^((-\d+(\.\d+)?)|(0+(\.0+)?))$”  //非正浮点数(负浮点数 + 0)
10        “^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$”  //负浮点数
11        “^(-?\d+)(\.\d+)?$”  //浮点数
12        “^[A-Za-z]+$”  //由26个英文字母组成的字符串
13        “^[A-Z]+$”  //由26个英文字母的大写组成的字符串
14        “^[a-z]+$”  //由26个英文字母的小写组成的字符串
15        “^[A-Za-z0-9]+$”  //由数字和26个英文字母组成的字符串
16        “^\w+$”  //由数字、26个英文字母或者下划线组成的字符串
17        “^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$”    //email地址
18        “^[a-zA-z]+://(\w+(-\w+)*)(\.(\w+(-\w+)*))*(\?\S*)?$”  //url
19        /^(d{2}|d{4})-((0([1-9]{1}))|(1[1|2]))-(([0-2]([1-9]{1}))|(3[0|1]))$/   //  年-月-日
20        /^((0([1-9]{1}))|(1[1|2]))/(([0-2]([1-9]{1}))|(3[0|1]))/(d{2}|d{4})$/   // 月/日/年
21        “^([w-.]+)@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.)|(([w-]+.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(]?)$”   //Emil
22        /^((\+?[0-9]{2,4}\-[0-9]{3,4}\-)|([0-9]{3,4}\-))?([0-9]{7,8})(\-[0-9]+)?$/     //电话号码
23        “^(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5])$”   //IP地址
24        匹配中文字符的正则表达式: [\u4e00-\u9fa5]
25        匹配双字节字符(包括汉字在内):[^\x00-\xff]
26        匹配空行的正则表达式:\n[\s| ]*\r
27        匹配HTML标记的正则表达式:/<(.*)>.*<\ 1=””>|<(.*)>/
28        匹配首尾空格的正则表达式:(^\s*)|(\s*$)
29        匹配Email地址的正则表达式:\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
30        匹配网址URL的正则表达式:^[a-zA-z]+://(\\w+(-\\w+)*)(\\.(\\w+(-\\w+)*))*(\\?\\S*)?$
31        匹配帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
32        匹配国内电话号码:(\d{3}-|\d{4}-)?(\d{8}|\d{7})?
33        匹配腾讯QQ号:^[1-9]*[1-9][0-9]*$
34        元字符及其在正则表达式上下文中的行为:
35        \ 将下一个字符标记为一个特殊字符、或一个原义字符、或一个后向引用、或一个八进制转义符。
36        ^ 匹配输入字符串的开始位置。如果设置了 RegExp 对象的Multiline 属性,^ 也匹配 ’\n’ 或 ’\r’ 之后的位置。
37        $ 匹配输入字符串的结束位置。如果设置了 RegExp 对象的Multiline 属性,$ 也匹配 ’\n’ 或 ’\r’ 之前的位置。
38        * 匹配前面的子表达式零次或多次。
39        + 匹配前面的子表达式一次或多次。+ 等价于 {1,}。
40        ? 匹配前面的子表达式零次或一次。? 等价于 {0,1}。
41        {n} n 是一个非负整数,匹配确定的n 次。
42        {n,} n 是一个非负整数,至少匹配n 次。
43        {n,m} m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。在逗号和两个数之间不能有空格。
44        ? 当该字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。
45        . 匹配除 “\n” 之外的任何单个字符。要匹配包括 ’\n’ 在内的任何字符,请使用象 ’[.\n]’ 的模式。
46        (pattern) 匹配pattern 并获取这一匹配。
47        (?:pattern) 匹配pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。
48        (?=pattern) 正向预查,在任何匹配 pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。
49        (?!pattern) 负向预查,与(?=pattern)作用相反
50        x|y 匹配 x 或 y。
51        [xyz] 字符集合。
52        [^xyz] 负值字符集合。
53        [a-z] 字符范围,匹配指定范围内的任意字符。
54        [^a-z] 负值字符范围,匹配任何不在指定范围内的任意字符。
55        \b 匹配一个单词边界,也就是指单词和空格间的位置。
56        \B 匹配非单词边界。
57        \cx 匹配由x指明的控制字符。
58        \d 匹配一个数字字符。等价于 [0-9]。
59        \D 匹配一个非数字字符。等价于 [^0-9]。
60        \f 匹配一个换页符。等价于 \x0c 和 \cL。

61        \n 匹配一个换行符。等价于 \x0a 和 \cJ。
62        \r 匹配一个回车符。等价于 \x0d 和 \cM。
63        \s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。
64        \S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。
65        \t 匹配一个制表符。等价于 \x09 和 \cI。
66        \v 匹配一个垂直制表符。等价于 \x0b 和 \cK。
67        \w 匹配包括下划线的任何单词字符。等价于’[A-Za-z0-9_]’。
68        \W 匹配任何非单词字符。等价于 ’[^A-Za-z0-9_]’。
69        \xn 匹配 n,其中 n 为十六进制转义值。十六进制转义值必须为确定的两个数字长。
70        \num 匹配 num,其中num是一个正整数。对所获取的匹配的引用。
71        \n 标识一个八进制转义值或一个后向引用。如果 \n 之前至少 n 个获取的子表达式,则 n 为后向引用。否则,如果 n 为八进制数字 (0-7),则 n 为一个八进制转义值。
72        \nm 标识一个八进制转义值或一个后向引用。如果 \nm 之前至少有is preceded by at least nm 个获取得子表达式,则 nm 为后向引用。如果 \nm 之前至少有 n 个获取,则 n 为一个后跟文字 m 的后向引用。如果前面的条件都不满足,若 n 和 m 均为八进制数字 (0-7),则 \nm 将匹配八进制转义值 nm。
73        \nml 如果 n 为八进制数字 (0-3),且 m 和 l 均为八进制数字 (0-7),则匹配八进制转义值 nml。
74        \un 匹配 n,其中 n 是一个用四个十六进制数字表示的Unicode字符。
75        匹配中文字符的正则表达式: [u4e00-u9fa5]
76        匹配双字节字符(包括汉字在内):[^x00-xff]
77        匹配空行的正则表达式:n[s| ]*r
78        匹配HTML标记的正则表达式:/<(.*)>.*|<(.*)>/
79        匹配首尾空格的正则表达式:(^s*)|(s*$)
80        匹配Email地址的正则表达式:w+([-+.]w+)*@w+([-.]w+)*.w+([-.]w+)*
81        匹配网址URL的正则表达式:http://([w-]+.)+[w-]+(/[w- ./?%&=]*)?
82        利用正则表达式限制网页表单里的文本框输入内容:
83        用正则表达式限制只能输入中文:
onkeyup=”value=value.replace(/[^u4E00-u9FA5]/g,”)”
84        用正则表达式限制只能输入全角字符:
onkeyup=”value=value.replace(/[^uFF00-uFFFF]/g,”)”
85        用正则表达式限制只能输入数字:
onkeyup=”value=value.replace(/[^d]/g,”) “onbeforepaste=”clipboardData.setData(‘text’,clipboardData.getData(‘text’).replace(/[^d]/g,”))”
86        用正则表达式限制只能输入数字和英文:
onkeyup=”value=value.replace(/[W]/g,”) “onbeforepaste=”clipboardData.setData(‘text’,clipboardData.getData(‘text’).replace(/[^d]/g,”))”
87        =========常用正则式
88        匹配中文字符的正则表达式: [\u4e00-\u9fa5]
89        匹配双字节字符(包括汉字在内):[^\x00-\xff]
90        匹配空行的正则表达式:\n[\s| ]*\r
91        匹配HTML标记的正则表达式:/<(.*)>.*<\ 1=””>|<(.*)>/
92        匹配首尾空格的正则表达式:(^\s*)|(\s*$)
93        匹配IP地址的正则表达式:/(\d+)\.(\d+)\.(\d+)\.(\d+)/g //
94        匹配Email地址的正则表达式:\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
95        匹配网址URL的正则表达式:http://(/[\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?
96        sql语句:^(select|drop|delete|create|update|insert).*$
97        非负整数:^\d+$
98        正整数:^[0-9]*[1-9][0-9]*$
99        非正整数:^((-\d+)|(0+))$
100        负整数:^-[0-9]*[1-9][0-9]*$
101        整数:^-?\d+$
102        非负浮点数:^\d+(\.\d+)?$
103        正浮点数:^((0-9)+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$
104        非正浮点数:^((-\d+\.\d+)?)|(0+(\.0+)?))$
105        负浮点数:^(-((正浮点数正则式)))$
106        英文字符串:^[A-Za-z]+$
107        英文大写串:^[A-Z]+$
108        英文小写串:^[a-z]+$
109        英文字符数字串:^[A-Za-z0-9]+$
110        英数字加下划线串:^\w+$
111        E-mail地址:^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$
112        URL:^[a-zA-Z]+://(\w+(-\w+)*)(\.(\w+(-\w+)*))*(\?\s*)?$
113        ^http:\/\/[A-Za-z0-9]+\.[A-Za-z0-9]+[\/=\?%\-&_~`@[\]\’:+!]*([^<>\”\”])*$
114        邮政编码:^[1-9]\d{5}$
115        中文:^[\u0391-\uFFE5]+$
116        电话号码:^((\(\d{2,3}\))|(\d{3}\-))?(\(0\d{2,3}\)|0\d{2,3}-)?[1-9]\d{6,7}(\-\d{1,4})?$
117        手机号码:^((\(\d{2,3}\))|(\d{3}\-))?13\d{9}$
118        双字节字符(包括汉字在内):^\x00-\xff
119        匹配首尾空格:(^\s*)|(\s*$)(像vbscript那样的trim函数)
120        匹配HTML标记:<(.*)>.*<\ 1=””>|<(.*)>
121        匹配空行:\n[\s| ]*\r
122        提取信息中的网络链接:(h|H)(r|R)(e|E)(f|F) *= *(‘|”)?(\w|\\|\/|\.)+(‘|”| *|>)?
123        提取信息中的邮件地址:\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
124        提取信息中的图片链接:(s|S)(r|R)(c|C) *= *(‘|”)?(\w|\\|\/|\.)+(‘|”| *|>)?
125        提取信息中的IP地址:(\d+)\.(\d+)\.(\d+)\.(\d+)
126        提取信息中的中国手机号码:(86)*0*13\d{9}

常见的PHP设计模式

工厂模式

最初在设计模式 一书中,许多设计模式都鼓励使用松散耦合。要理解这个概念,让我们最好谈一下许多开发人员从事大型系统的艰苦历程。在更改一个代码片段时,就会发生问题,系统其他部分 —— 您曾认为完全不相关的部分中也有可能出现级联破坏。

该问题在于紧密耦合 。系统某个部分中的函数和类严重依赖于系统的其他部分中函数和类的行为和结构。您需要一组模式,使这些类能够相互通信,但不希望将它们紧密绑定在一起,以避免出现联锁。

在大型系统中,许多代码依赖于少数几个关键类。需要更改这些类时,可能会出现困难。例如,假设您有一个从文件读取的 User 类。您希望将其更改为从数据库读取的其他类,但是,所有的代码都引用从文件读取的原始类。这时候,使用工厂模式会很方便。

工厂模式 是一种类,它具有为您创建对象的某些方法。您可以使用工厂类创建对象,而不直接使用 new。这样,如果您想要更改所创建的对象类型,只需更改该工厂即可。使用该工厂的所有代码会自动更改。

清单 1 显示工厂类的一个示列。等式的服务器端包括两个部分:数据库和一组 PHP 页面,这些页面允许您添加反馈、请求反馈列表并获取与特定反馈相关的文章。

清单 1. Factory1.php

IUser 接口定义用户对象应执行什么操作。IUser 的实现称为 User,UserFactory 工厂类则创建 IUser 对象。此关系可以用图 1 中的 UML 表示。

图 1. 工厂类及其相关 IUser 接口和用户类

常见的PHP设计模式

如果您使用 php 解释器在命令行上运行此代码,将得到如下结果:

测试代码会向工厂请求 User 对象,并输出 getName 方法的结果。

有 一种工厂模式的变体使用工厂方法。类中的这些公共静态方法构造该类型的对象。如果创建此类型的对象非常重要,此方法非常有用。例如,假设您需要先创建对 象,然后设置许多属性。此版本的工厂模式会将该进程封装在单个位置中,这样,不用复制复杂的初始化代码,也不必将复制好的代码在在代码库中到处粘贴。

清单 2 显示使用工厂方法的一个示例。

清单 2. Factory2.php

这段代码要简单得多。它仅有一个接口 IUser 和一个实现此接口的 User 类。User 类有两个创建对象的静态方法。此关系可用图 2 中的 UML 表示。

图 2. IUser 接口和带有工厂方法的 user 类

常见的PHP设计模式

在命令行中运行脚本产生的结果与清单 1 的结果相同,如下所示:

如上所述,有时此类模式在规模较小的环境中似乎有些大材小用。不过,最好还是学习这种扎实的编码形式,以便应用于任意规模的项目中。

单元素模式

某些应用程序资源是独占的,因为有且只有一个此类型的资源。例如,通过数据库句柄到数据库的连接是独占的。您希望在应用程序中共享数据库句柄,因为在保持连接打开或关闭时,它是一种开销,在获取单个页面的过程中更是如此。

单元素模式可以满足此要求。如果应用程序每次包含且仅包含一个对象,那么这个对象就是一个单元素(Singleton)。清单 3 中的代码显示了 PHP V5 中的一个数据库连接单元素。

清单 3. Singleton.php

此代码显示名为 DatabaseConnection 的单个类。您不能创建自已的 DatabaseConnection,因为构造函数是专用的。但使用静态 get 方法,您可以获得且仅获得一个 DatabaseConnection 对象。此代码的 UML 如图 3 所示。

图 3. 数据库连接单元素

常见的PHP设计模式

在两次调用间,handle 方法返回的数据库句柄是相同的,这就是最好的证明。您可以在命令行中运行代码来观察这一点。

返回的两个句柄是同一对象。如果您在整个应用程序中使用数据库连接单元素,那么就可以在任何地方重用同一句柄。

您可以使用全局变量存储数据库句柄,但是,该方法仅适用于较小的应用程序。在较大的应用程序中,应避免使用全局变量,并使用对象和方法访问资源。

观察者模式

观察者模式为您提供了避免组件之间紧密耦合的另一种方法。该模式非常简单:一个对象通过添加一个方法(该方法允许另一个对象,即观察者注册自己)使本身变得可观察。当可观察的对象更改时,它会将消息发送到已注册的观察者。这些观察者使用该信息执行的操作与可观察的对象无关。结果是对象可以相互对话,而不必了解原因。

一个简单示例是系统中的用户列表。清单 4 中的代码显示一个用户列表,添加用户时,它将发送出一条消息。添加用户时,通过发送消息的日志观察者可以观察此列表。

清单 4. Observer.php

此代码定义四个元素:两个接口和两个类。IObservable 接口定义可以被观察的对象,UserList 实现该接口,以便将本身注册为可观察。IObserver 列表定义要通过怎样的方法才能成为观察者,UserListLogger 实现 IObserver 接口。图 4 的 UML 中展示了这些元素。

图 4. 可观察的用户列表和用户列表事件日志程序

常见的PHP设计模式

如果在命令行中运行它,您将看到以下输出:

测试代码创建 UserList,并将 UserListLogger 观察者添加到其中。然后添加一个消费者,并将这一更改通知 UserListLogger。

认识到 UserList 不知道日志程序将执行什么操作很关键。可能存在一个或多个执行其他操作的侦听程序。例如,您可能有一个向新用户发送消息的观察者,欢迎新用户使用该系统。这种方法的价值在于 UserList 忽略所有依赖它的对象,它主要关注在列表更改时维护用户列表并发送消息这一工作。

此模式不限于内存中的对象。它是在较大的应用程序中使用的数据库驱动的消息查询系统的基础。

命令链模式

命令链 模式以松散耦合主题为基础,发送消息、命令和请求,或通过一组处理程序发送任意内容。每个处理程序都会自行判断自己能否处理请求。如果可以,该请求被处理,进程停止。您可以为系统添加或移除处理程序,而不影响其他处理程序。清单 5 显示了此模式的一个示例。

清单 5. Chain.php

此代码定义维护 ICommand 对象列表的 CommandChain 类。两个类都可以实现 ICommand 接口 —— 一个对邮件的请求作出响应,另一个对添加用户作出响应。 图 5 给出了 UML。

图 5. 命令链及其相关命令

常见的PHP设计模式

如果您运行包含某些测试代码的脚本,则会得到以下输出:

代码首先创建 CommandChain 对象,并为它添加两个命令对象的实例。然后运行两个命令以查看谁对这些命令作出了响应。如果命令的名称匹配 UserCommand 或 MailCommand,则代码失败,不发生任何操作。

为处理请求而创建可扩展的架构时,命令链模式很有价值,使用它可以解决许多问题。

策略模式

我们讲述的最后一个设计模式是策略 模 式。在此模式中,算法是从复杂类提取的,因而可以方便地替换。例如,如果要更改搜索引擎中排列页的方法,则策略模式是一个不错的选择。思考一下搜索引擎的 几个部分 —— 一部分遍历页面,一部分对每页排列,另一部分基于排列的结果排序。在复杂的示例中,这些部分都在同一个类中。通过使用策略模式,您可将排列部分放入另一个 类中,以便更改页排列的方式,而不影响搜索引擎的其余代码。

作为一个较简单的示例,清单 6 显示了一个用户列表类,它提供了一个根据一组即插即用的策略查找一组用户的方法。

清单 6. Strategy.php

此代码的 UML 如图 6 所示。

图 6. 用户列表和用于选择用户的策略

常见的PHP设计模式

UserList 类是打包名称数组的一个包装器。它实现 find 方法,该方法利用几个策略之一来选择这些名称的子集。这些策略由 IStrategy接口定义,该接口有两个实现:一个随机选择用户,另一个根据指定名称选择其后的所有名称。运行测试代码时,将得到以下输出:

测试代码为两个策略运行同一用户列表,并显示结果。在第一种情况中,策略查找排列在 J 后的任何名称,所以您将得到 Jack、Lori 和 Megan。第二个策略随机选取名称,每次会产生不同的结果。在这种情况下,结果为 Andy 和 Megan。

策略模式非常适合复杂数据管理系统或数据处理系统,二者在数据筛选、搜索或处理的方式方面需要较高的灵活性。

学习正则技巧

1、^ 和 $ ,分别用来匹配字符串的开始和结束,举例说明如下:

“^abc$”:就是要求以abc开头和以abc结尾的字符串,实际上是只有abc匹配;

2、‘*’ ‘+’ 和 ‘?’他们用来表示一个字符可以出现的次数或者顺序,他们分别表示:

“zero or more”相当于{0,},“ab*”:和ab{0,}同义,匹配以a开头,后面可以接0个或者N个b组成的字符串(”a”, “ab”, “abbb”, 等);
“one or more”相当于{1,},“ab+”:和ab{1,}同义,同上条一样,但最少要有一个b存在 (”ab” “abbb”等);
“zero or one.”相当于{0,1},“ab?”:和ab{0,1}同义,可以没有或者只有一个b;
“a?b+$”:匹配以一个或者0个a再加上一个以上的b结尾的字符串。

要点:’*’ ‘+’ 和 ‘?’ 只管它前面那个字符。

3、大括号{}

“ab{2}”: 要求a后面一定要跟两个b(一个也不能少)(”abb”);
“ab{2,}”: 要求a后面一定要有两个或者两个以上b(如”abb” “abbbb” 等);
“ab{3,5}”: 要求a后面可以有2-5个b(”abbb”, “abbbb”, or “abbbbb”)。    看了上面的例子,你对{n,m}应该理解了吧。要注意的是,n和m都不能为负整数,而且n总是小于m。这样,才能最少匹配n次且最多匹配m次,如”p{1,5}”将匹配 “pvpppppp”中的前五个p。

4、小括号()
( )    合并整体匹配,并放入内存,可使用\1 \2…依次获取;
“a(bc)*”: 匹配 a 后面跟0个或者一个”bc”;
“a(bc){1,5}”: 一个到5个 “bc”;

5、字符 ‘|’,相当于OR操作

“hi|hello”: 匹配含有”hi” 或者 “hello” 的字符串;
“(b|cd)ef”: 匹配含有 “bef” 或者 “cdef”的字符串;
“(a|b)*c”: 匹配含有这样多个(包括0个)a或b,后面跟一个c的字符串;

6、一个点’.’

可以代表所有的单一字符,不包括”\n”
如果,要匹配包括”\n”在内的所有单个字符,怎么办?用’[\n.]’这种模式。
“a.[0-9]”: 一个a加一个字符再加一个0到9的数字;
“^.{3}$”: 三个任意字符结尾。

7、中括号括住的内容只匹配一个单一的字符

“[ab]”: 匹配单个的 a 或者 b ( 和 “a│b” 一样);
“[a-d]”: 匹配’a’ 到’d’的单个字符 (和”a│b│c│d” 还有 “[abcd]”效果一样);
一般我们都用[a-zA-Z]来指定字符为一个大小写英文:
“^[a-zA-Z]”: 匹配以大小写字母开头的字符串;
“[0-9]%”: 匹配含有形如 x% 的字符串;
“,[a-zA-Z0-9]$”: 匹配以逗号再加一个数字或字母结尾的字符串;

你也可以把你不想要得字符列在中括号里,你只需要在总括号里面使用 ’^’作为开头
“%[^a-zA-Z]%”:匹配含有两个百分号里面有一个非字母的字符串。

要点:^用在中括号开头的时候,就表示排除括号里的字符。
为了PHP能够解释,你必须在这些字符面前后加双引号,并且将一些字符转义。
在中括号里面,所有的特殊字符,包括(”),都将失去他们的特殊性质,“[*\+?{}.]”匹配含有这些字符的字符串。

8、以\开头的

\b     出现在目标字符串的开头或结尾的两个边界之一,边界可以是空格或者特殊字符;比如’ve\b’,可以匹配love里的ve而不匹配very里有ve。
\B     对象必须位于目标字符串的开头和结尾两个边界之内,即匹配对象既不能作为目标字符串的开头,也不能作为目标字符串的结尾;
\s:   用于匹配单个空格符,包括tab键和换行符,等价于[\f\t\v];
\S:   用于匹配除单个空格符之外的任何一个字符,[^\f\t\v];
\d:   匹配从0到9的一个数字,等价于[0-9];
\D     匹配除数字以外任何一个字符,等价于[^0-9];
\w:   用于匹配一个字母、数字或下划线字符,等价于[0-9a-zA-Z_];
\W:   用于匹配所有除英文字母、数字和下划线以外任何的一个字符,[^0-9a-zA-Z_];
\A     匹配字符串串首的原子
\Z     匹配字符串串尾的原子
\f     匹配一个换页符等价于 \x0c 或 \cL
匹配一个换行符;等价于 \x0a 或 \cJ
匹配一个回车符等价于\x0d 或 \cM
\t     匹配一个制表符;等价于 \x09\或\cl
\v     匹配一个垂直制表符;等价于\x0b或\ck
\oNN   匹配一个八进制数字
\xNN   匹配一个十六进制数字
\cC    匹配一个控制字符

9、指定某一个范围而不局限于具体的字符

/[A-Z]/
上述正则表达式将会与从A到Z范围内任何一个大写字母相匹配。
/[a-z]/
上述正则表达式将会与从a到z范围内任何一个小写字母相匹配。
/[0-9]/
上述正则表达式将会与从0到9范围内任何一个数字相匹配。
/([a-z][A-Z][0-9])+/

———————————–手把手教你如何撰写正则表达式————————————-

经典正则表达式应用1:如何构建一个模式来匹配货币数量的输入。

构建一个匹配模式去检查输入的信息是否为一个表示money的数字。
我们认为一个表示money的数量有四种方式:“10000.00”和“10,000.00”,或者没有小数部分,“10000”and“10,000”。
现在让我们开始构建这个匹配模式:

^[1-9][0-9]*$
这是所变量必须以非0的数字开头,但这也意味着单一的”0″也不能通过测试,以下是解决的方法:
^(0|[1-9][0-9]*)$
“只有0和不以0开头的数字与之匹配”,我们也可以允许一个负号在数字之前:
^(0|-?[1-9][0-9]*)$
这就是:0或者一个以0开头且可能有一个负号在前面的数字。
好了,现在让我们别那么严谨,允许以0开头。现在让我们放弃负号,因为我们在表示钱币的时候并不需要用到。我们现在指定模式用来匹配小数部分:
^[0-9]+(\.[0-9]+)?$
这暗示匹配的字符串必须最少以一个阿拉伯数字开头。但是注意,在上面模式中“10.”是不匹配的, 只有“10”和“10.2”才可以,你知道为什么吗?
^[0-9]+(\.[0-9]{2})?$
我们上面指定小数点后面必须有两位小数。如果你认为这样太苛刻,你可以改成:
^[0-9]+(\.[0-9]{1,2})?$
这将允许小数点后面有一到两个字符。现在我们加上用来增加可读性的逗号(每隔三位),我们可以这样表示:
^[0-9]{1,3}(,[0-9]{3})*(\.[0-9]{1,2})?$

不要忘记’+’可以被’*’替代如果你想允许空白字符串被输入话,也不要忘记反斜杆’\’在php字符串中可能会出现错误 (很普遍的错误)。

经典正则表达式应用2:构造检查email的正则表达式
在一个完整的email地址中有三个部分:
1. 用户名 (在‘@’左边的一切) 2.’@’,3. 服务器名
用户名可以含有大小写字母阿拉伯数字,句号(’.’)减号(’-’)and下划线’_’),服务器名字也是符合这个规则,当然下划线除外。
现在,用户名的开始和结束都不能是句点,服务器也是这样,还有你不能有两个连续的句点他们之间至少存在一个字符。
好现在我们来看一下怎么为用户名写一个匹配模式:

^[_a-zA-Z0-9-]+$
现在还不能允许句号的存在,我们把它加上:
^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*$
上面的意思就是说:以至少一个规范字符(除了.)开头,后面跟着0个或者多个以点开始的字符串。
简单化一点,我们可以用eregi()取代ereg(),eregi()对大小写不敏感, 我们就不需要指定两个范围“a-z”和“A-Z”只需要指定一个就可以了:
^[_a-z0-9-]+(\.[_a-z0-9-]+)*$
后面的服务器名字也是一样,但要去掉下划线:
^[a-z0-9-]+(\.[a-z0-9-]+)*$
好,现在只需要用”@”把两部分连接:
^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*$
这就是完整的email认证匹配模式了,只需要调用:
eregi(”^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*$”,$eamil)

就可以得到是否为email了

——————————-正则表达式的其他用法———————————–

提取字符串

ereg() and eregi() 有一个特性是允许用户通过正则表达式去提取字符串的一部分。
比如说,我们想从 path/URL 提取文件名,下面的代码就是你需要:

ereg(”([^\\/]*)$”, $pathOrUrl, $regs);
echo $regs[1];

高级的代换

ereg_replace() 和 eregi_replace()也是非常有用的,假如我们想把所有的间隔负号都替换成逗号:
ereg_replace(”[ \n\r\t]+”, “,”, trim($str));

PSR-6 缓存接口规范

缓存是提升应用性能的常用手段,为框架中最通用的功能,每个框架也都推出专属的、功能多 样的缓存库。这些差别使得开发人员不得不学习多种系统,而很多可能是他们并不需要的功能。 此外,缓存库的开发者同样面临着一个窘境,是只支持有限数量的几个框架还是创建一堆庞 大的适配器类。

一个通用的缓存系统接口可以解决掉这些问题。库和框架的开发人员能够知道缓存系统会按照他们所 预期的方式工作,缓存系统的开发人员只需要实现单一的接口,而不用去开发各种各样的适配器。

目标

本 PSR 的目标是:创建一套通用的接口规范,能够让开发人员整合到现有框架和系统,而不需要去 开发框架专属的适配器类。

关于「能愿动词」的使用

为了避免歧义,文档大量使用了「能愿动词」,对应的解释如下:

  • 必须 (MUST):绝对,严格遵循,请照做,无条件遵守;
  • 一定不可 (MUST NOT):禁令,严令禁止;
  • 应该 (SHOULD) :强烈建议这样做,但是不强求;
  • 不该 (SHOULD NOT):强烈不建议这样做,但是不强求;
  • 可以 (MAY)可选 (OPTIONAL) :选择性高一点,在这个文档内,此词语使用较少;

参见:RFC 2119

定义

  • 调用类库 (Calling Library) – 调用者,使用缓存服务的类库,这个类库调用缓存服务,调用的 是此缓存接口规范的具体「实现类库」,调用者不需要知道任何「缓存服务」的具体实现。
  • 实现类库 (Implementing Library) – 此类库是对「缓存接口规范」的具体实现,封装起来的缓存服务,供「调用类库」使用。实现类库 必须 提供 PHP 类来实现 Cache\CacheItemPoolInterfaceCache\CacheItemInterface 接口。 实现类库 必须 支持最小的如下描述的 TTL 功能,秒级别的精准度。
  • 生存时间值 (TTL – Time To Live) – 定义了缓存可以存活的时间,以秒为单位的整数值。
  • 过期时间 (Expiration) – 定义准确的过期时间点,一般为缓存存储发生的时间点加上 TTL 时 间值,也可以指定一个 DateTime 对象。

    假如一个缓存项的 TTL 设置为 300 秒,保存于 1:30:00 ,那么缓存项的过期时间为 1:35:00。

    实现类库 可以 让缓存项提前过期,但是 必须 在到达过期时间时立即把缓存项标示为 过期。如果调用类库在保存一个缓存项的时候未设置「过期时间」、或者设置了 null 作为过期 时间(或者 TTL 设置为 null),实现类库 可以 使用默认自行配置的一个时间。如果没 有默认时间,实现类库 必须把存储时间当做 永久性 存储,或者按照底层驱动能支持的 最长时间作为保持时间。

  • 键 (KEY) – 长度大于 1 的字串,用作缓存项在缓存系统里的唯一标识符。实现类库 必须 支持「键」规则 A-Z, a-z, 0-9, _, 和 . 任何顺序的 UTF-8 编码,长度 小于 64 位。实现类库 可以 支持更多的编码或者更长的长度,不过 必须 支持至少以上指定 的编码和长度。实现类库可自行实现对「键」的转义,但是 必须 保证能够无损的返回「键」字串。以下 的字串作为系统保留: {}()/\@:一定不可 作为「键」的命名支持。
  • 命中 (Hit) – 一个缓存的命中,指的是当调用类库使用「键」在请求一个缓存项的时候,在缓存 池里能找到对应的缓存项,并且此缓存项还未过期,并且此数据不会因为任何原因出现错误。调用类 库 应该 确保先验证下 isHit()有命中后才调用 get() 获取数据。
  • 未命中 (Miss) – 一个缓存未命中,是完全的上面描述的「命中」的相反。指的是当调用类库使用「键」在请求一个缓存项的时候,在缓存池里未能找到对应的缓存项,或者此缓存项已经过期,或者此数据因为任何原因出现错误。一个过期的缓存项,必须 被当做 未命中 来对待。
  • 延迟 (Deferred) – 一个延迟的缓存,指的是这个缓存项可能不会立刻被存储到物理缓存池里。一个 缓存池对象 可以对一个指定延迟的缓存项进行延迟存储,这样做的好处是可以利用一些缓存服务器提供 的批量插入功能。缓存池 必须 能对所有延迟缓存最终能持久化,并且不会丢失。可以 在调用类库还未发起保存请求之前就做持久化。当调用类库调用 commit() 方法时,所有的延迟缓存都 必须 做持久化。实现类库 可以 自行决定使用什么逻辑来触发数据持久化,如对象的 析构方法 (destructor) 内、调用 save() 时持久化、倒计时保存或者触及最大数量时保存等。当请求一个延迟 缓存项时,必须 返回一个延迟,未持久化的缓存项对象。

数据

实现类库 必须 支持所有的可序列化的 PHP 数据类型,包含:

  • 字符串 – 任何大小的 PHP 兼容字符串
  • 整数 – PHP 支持的低于 64 位的有符号整数值
  • 浮点数 – 所有的有符号浮点数
  • 布尔 – true 和 false.
  • Nullnull
  • 数组 – 各种形式的 PHP 数组
  • 对象(Object) – 所有的支持无损序列化和反序列化的对象,如: $o == unserialize(serialize($o)) 。对象 可以 使用 PHP 的 Serializable 接口, __sleep() 或者 __wakeup() 魔术方法,或者在合适的情况下,使用其他类似的语言特性。

所有存进实现类库的数据,都 必须 能做到原封不动的取出。连类型也 必须 是完全一致,如果 存进缓存的是字符串 5,取出来的却是整数值 5 的话,可以算作严重的错误。实现类库 可以 使用 PHP 的「serialize()/unserialize() 方法」作为底层实现,不过不强迫这样做。对于他们的兼容性,以能支持所有数据类型作为基准线。

实在无法「完整取出」存入的数据的话,实现类库 必须 把「缓存丢失」标示作为返回,而不是损坏了的数据。

主要概念

缓存池 Pool

缓存池包含缓存系统里所有缓存数据的集合。缓存池逻辑上是所有缓存项存储的仓库,所有存储进去的数据, 都能从缓存池里取出来,所有的对缓存的操作,都发生在缓存池子里。

缓存项 Items

一条缓存项在缓存池里代表了一对「键/值」对应的数据,「键」被视为每一个缓存项主键,是缓存项的 唯一标识符,必须是不可变更的,当然,「值」可以 任意变更。

错误处理

缓存对应用性能起着至关重要的作用,但是,无论在任何情况下,缓存 一定不可 作为应用程序不 可或缺的核心功能。

缓存系统里的错误 一定不可 导致应用程序故障,所以,实现类库 一定不可 抛出任何除了 此接口规范定义的以外的异常,并且 必须 捕捉包括底层存储驱动抛出的异常,不让其冒泡至超 出缓存系统内。

实现类库 应该 对此类错误进行记录,或者以任何形式通知管理员。

调用类库发起删除缓存项的请求,或者清空整个缓冲池子的请求,「键」不存在的话 必须 不能 当成是有错误发生。后置条件是一样的,如果取数据时,「键」不存在的话 必须 不能当成是有错误发生

接口

CacheItemInterface

CacheItemInterface 定义了缓存系统里的一个缓存项。每一个缓存项 必须 有一个「键」与之相 关联,此「键」通常是通过 Cache\CacheItemPoolInterface 来设置。

Cache\CacheItemInterface 对象把缓存项的存储进行了封装,每一个 Cache\CacheItemInterface 由一个 Cache\CacheItemPoolInterface 对象生成,CacheItemPoolInterface 负责一些必须的设置,并且给对象设置具有 唯一性的「键」。

Cache\CacheItemInterface 对象 必须 能够存储和取出任何类型的,在「数据」章节定义的 PHP 数值。

调用类库 一定不可 擅自初始化「CacheItemInterface」对象,「缓存项」只能使用「CacheItemPoolInterface」对象的 getItem() 方法来获取。调用类库 一定不可 假设 由一个实现类库创建的「缓存项」能被另一个实现类库完全兼容。

CacheItemPoolInterface

Cache\CacheItemPoolInterface 的主要目的是从调用类库接收「键」,然后返回对应的 Cache\CacheItemInterface 对象。

此接口也是作为主要的,与整个缓存集合交互的方式。所有的配置和初始化由实现类库自行实现。

CacheException

此异常用于缓存系统发生的所有严重错误,包括但不限制于 缓存系统配置,如连接到缓存服务器出错、错 误的用户身份认证等。

所有的实现类库抛出的异常都 必须 实现此接口。

InvalidArgumentException

PSR-4 自动加载规范

本 PSR 是关于由文件路径 自动载入 对应类的相关规范, 本规范是可互操作的,可以作为任一自动载入规范的补充,其中包括 PSR-0,此外, 本 PSR 还包括自动载入的类对应的文件存放路径规范。

关于「能愿动词」的使用

为了避免歧义,文档大量使用了「能愿动词」,对应的解释如下:

  • 必须 (MUST):绝对,严格遵循,请照做,无条件遵守;
  • 一定不可 (MUST NOT):禁令,严令禁止;
  • 应该 (SHOULD) :强烈建议这样做,但是不强求;
  • 不该 (SHOULD NOT):强烈不建议这样做,但是不强求;
  • 可以 (MAY)可选 (OPTIONAL) :选择性高一点,在这个文档内,此词语使用较少;

参见:RFC 2119

2. 详细说明

  1. 此处的「类」泛指所有的「Class类」、「接口」、「traits 可复用代码块」以及其它类似结构。
  2. 一个完整的类名需具有以下结构:

    1. 完整的类名 必须 要有一个顶级命名空间,被称为 “vendor namespace”;
    2. 完整的类名 可以 有一个或多个子命名空间;
    3. 完整的类名 必须 有一个最终的类名;
    4. 完整的类名中任意一部分中的下滑线都是没有特殊含义的;
    5. 完整的类名 可以 由任意大小写字母组成;
    6. 所有类名都 必须 是大小写敏感的。
  3. 当根据完整的类名载入相应的文件
    1. 完整的类名中,去掉最前面的命名空间分隔符,前面连续的一个或多个命名空间和子命名空间,作为「命名空间前缀」,其必须与至少一个「文件基目录」相对应;
    2. 紧接命名空间前缀后的子命名空间 必须 与相应的「文件基目录」相匹配,其中的命名空间分隔符将作为目录分隔符。
    3. 末尾的类名 必须 与对应的以 .php 为后缀的文件同名。
    4. 自动加载器(autoloader)的实现 一定不可 抛出异常、一定不可 触发任一级别的错误信息以及 不应该 有返回值。

关于本规范的实现,可参阅 相关实例

注意:实例并 属于规范的一部分,且随时 有所变动。

PSR-2 编码风格规范

本篇规范是 [PSR-1]基本代码规范的继承与扩展。

本规范希望通过制定一系列规范化PHP代码的规则,以减少在浏览不同作者的代码时,因代码风格的不同而造成不便。

当多名程序员在多个项目中合作时,就需要一个共同的编码规范, 而本文中的风格规范源自于多个不同项目代码风格的共同特性, 因此,本规范的价值在于我们都遵循这个编码风格,而不是在于它本身。

关于「能愿动词」的使用

为了避免歧义,文档大量使用了「能愿动词」,对应的解释如下:

  • 必须 (MUST):绝对,严格遵循,请照做,无条件遵守;
  • 一定不可 (MUST NOT):禁令,严令禁止;
  • 应该 (SHOULD)> :强烈建议这样做,但是不强求;
  • 不该 (SHOULD NOT):强烈不建议这样做,但是不强求;
  • 可以 (MAY) 和 可选 (OPTIONAL) :选择性高一点,在这个文档内,此词语使用较少;

参见:RFC 2119

1. 概览

  • 代码 必须 遵循 PSR-1 中的编码规范 。
  • 代码 必须 使用4个空格符而不是「Tab 键」进行缩进。
  • 每行的字符数 应该 软性保持在 80 个之内,理论上 一定不可 多于 120 个,但 一定不可 有硬性限制。
  • 每个 namespace 命名空间声明语句和 use 声明语句块后面,必须 插入一个空白行。
  • 类的开始花括号({) 必须 写在函数声明后自成一行,结束花括号(})也 必须 写在函数主体后自成一行。
  • 方法的开始花括号({) 必须 写在函数声明后自成一行,结束花括号(})也 必须 写在函数主体后自成一行。
  • 类的属性和方法 必须 添加访问修饰符(private、protected 以及 public),abstract 以及final 必须 声明在访问修饰符之前,而 static 必须 声明在访问修饰符之后。
  • 控制结构的关键字后 必须 要有一个空格符,而调用方法或函数时则 一定不可 有。
  • 控制结构的开始花括号({) 必须 写在声明的同一行,而结束花括号(}) 必须 写在主体后自成一行。
  • 控制结构的开始左括号后和结束右括号前,都 一定不可 有空格符。

1.1. 例子

以下例子程序简单地展示了以上大部分规范:

2. 通则

2.1 基本编码准则

代码 必须 符合 PSR-1 中的所有规范。

2.2 文件

所有PHP文件 必须 使用 Unix LF (linefeed) 作为行的结束符。

所有PHP文件 必须 以一个空白行作为结束。

纯PHP代码文件 必须 省略最后的 ?> 结束标签。

2.3. 行

行的长度 一定不可 有硬性的约束。

软性的长度约束 必须 要限制在 120 个字符以内,若超过此长度,带代码规范检查的编辑器 必须 要发出警告,不过 一定不可 发出错误提示。

每行 不该 多于80个字符,大于80字符的行 应该 折成多行。

非空行后 一定不可 有多余的空格符。

空行 可以 使得阅读代码更加方便以及有助于代码的分块。

每行 一定不可 存在多于一条语句。

2.4. 缩进

代码 必须 使用4个空格符的缩进,一定不可 用 tab键。

备注:使用空格而不是「tab键缩进」的好处在于, 避免在比较代码差异、打补丁、重阅代码以及注释时产生混淆。 并且,使用空格缩进,让对齐变得更方便。

2.5. 关键字 以及 True/False/Null

PHP所有 关键字 必须 全部小写。

常量 true 、false 和 null 也 必须 全部小写。

3. namespace 以及 use 声明

namespace 声明后 必须 插入一个空白行。

所有 use 必须 在 namespace 后声明。

每条 use 声明语句 必须 只有一个 use 关键词。

use 声明语句块后 必须 要有一个空白行。

例如:

4. 类、属性和方法

此处的「类」泛指所有的「class类」、「接口」以及「traits 可复用代码块」。

4.1. 扩展与继承

关键词 extends和 implements 必须 写在类名称的同一行。

类的开始花括号 必须 独占一行,结束花括号也 必须 在类主体后独占一行。

implements 的继承列表也 可以 分成多行,这样的话,每个继承接口名称都 必须 分开独立成行,包括第一个。

4.2. 属性

每个属性都 必须 添加访问修饰符。

一定不可 使用关键字 var 声明一个属性。

每条语句 一定不可 定义超过一个属性。

不该 使用下划线作为前缀,来区分属性是 protected 或 private。

以下是属性声明的一个范例:

4.3. 方法

所有方法都 必须 添加访问修饰符。

不该 使用下划线作为前缀,来区分方法是 protected 或 private。

方法名称后 一定不可 有空格符,其开始花括号 必须 独占一行,结束花括号也 必须 在方法主体后单独成一行。参数左括号后和右括号前 一定不可 有空格。

一个标准的方法声明可参照以下范例,留意其括号、逗号、空格以及花括号的位置。

4.4. 方法的参数

参数列表中,每个逗号后面 必须 要有一个空格,而逗号前面 一定不可 有空格。

有默认值的参数,必须 放到参数列表的末尾。

参数列表 可以 分列成多行,这样,包括第一个参数在内的每个参数都 必须 单独成行。

拆分成多行的参数列表后,结束括号以及方法开始花括号 必须 写在同一行,中间用一个空格分隔。

4.5. abstract 、 final 、 以及 static

需要添加 abstract 或 final 声明时,必须 写在访问修饰符前,而 static 则 必须 写在其后。

4.6. 方法及函数调用

方法及函数调用时,方法名或函数名与参数左括号之间 一定不可 有空格,参数右括号前也 一定不可 有空格。每个参数前 一定不可 有空格,但其后 必须 有一个空格。

参数 可以 分列成多行,此时包括第一个参数在内的每个参数都 必须 单独成行。

5. 控制结构

控制结构的基本规范如下:

  • 控制结构关键词后 必须 有一个空格。
  • 左括号 ( 后 一定不可 有空格。
  • 右括号 ) 前也 一定不可 有空格。
  • 右括号 ) 与开始花括号 { 间 必须 有一个空格。
  • 结构体主体 必须 要有一次缩进。
  • 结束花括号 } 必须 在结构体主体后单独成行。

每个结构体的主体都 必须 被包含在成对的花括号之中, 这能让结构体更加结构话,以及减少加入新行时,出错的可能性。

5.1. if 、elseif 和 else

标准的 if 结构如下代码所示,请留意「括号」、「空格」以及「花括号」的位置, 注意 else 和 elseif 都与前面的结束花括号在同一行。

应该 使用关键词 elseif 代替所有 else if ,以使得所有的控制关键字都像是单独的一个词。

5.2. switch 和 case

标准的 switch 结构如下代码所示,留意括号、空格以及花括号的位置。 case 语句 必须 相对 switch 进行一次缩进,而 break 语句以及 case 内的其它语句都 必须 相对 case 进行一次缩进。

如果存在非空的 case 直穿语句,主体里 必须 有类似 // no break 的注释。

5.3. whlie 和 do whlie

一个规范的 while 语句应该如下所示,注意其「括号」、「空格」以及「花括号」的位置。

标准的 whlie 语句如下所示,同样的,注意其「括号」、「空格」以及「花括号」的位置。

5.4. for

标准的 for 语句如下所示,注意其「括号」、「空格」以及「花括号」的位置。

5.5. foreach

标准的 foreach 语句如下所示,注意其「括号」、「空格」以及「花括号」的位置。

5.6. try, catch

标准的 try catch 语句如下所示,注意其「括号」、「空格」以及「花括号」的位置。

6. 闭包

闭包声明时,关键词 function 后以及关键词 use 的前后都 必须 要有一个空格。

开始花括号 必须 写在声明的同一行,结束花括号 必须 紧跟主体结束的下一行。

参数列表和变量列表的左括号后以及右括号前,一定不可 有空格。

参数和变量列表中,逗号前 一定不可 有空格,而逗号后 必须 要有空格。

闭包中有默认值的参数 必须 放到列表的后面。

标准的闭包声明语句如下所示,注意其「括号」、「空格」以及「花括号」的位置。

参数列表以及变量列表 可以 分成多行,这样,包括第一个在内的每个参数或变量都 必须 单独成行,而列表的右括号与闭包的开始花括号 必须 放在同一行。

以下几个例子,包含了参数和变量列表被分成多行的多情况。

注意,闭包被直接用作函数或方法调用的参数时,以上规则仍然适用。

PSR-1 基础编码规范

基本代码规范

本篇规范制定了代码基本元素的相关标准,以确保共享的PHP代码间具有较高程度的技术互通性。

关于「能愿动词」的使用

为了避免歧义,文档大量使用了「能愿动词」,对应的解释如下:

  • 必须 (MUST):绝对,严格遵循,请照做,无条件遵守;
  • 一定不可 (MUST NOT):禁令,严令禁止;
  • 应该 (SHOULD) :强烈建议这样做,但是不强求;
  • 不该 (SHOULD NOT):强烈不建议这样做,但是不强求;
  • 可以 (MAY)可选 (OPTIONAL) :选择性高一点,在这个文档内,此词语使用较少;

参见:RFC 2119

1. 概览

  • PHP代码文件 必须<?php<?= 标签开始;
  • PHP代码文件 必须不带 BOM UTF-8 编码;
  • PHP代码中 应该 只定义类、函数、常量等声明,或其他会产生 副作用 的操作(如:生成文件输出以及修改 .ini 配置文件等),二者只能选其一;
  • 命名空间以及类 必须 符合 PSR 的自动加载规范:PSR-4 中的一个;
  • 类的命名 必须 遵循 StudlyCaps 大写开头的驼峰命名规范;
  • 类中的常量所有字母都 必须 大写,单词间用下划线分隔;
  • 方法名称 必须 符合 camelCase 式的小写开头驼峰命名规范。

2. 文件

2.1. PHP标签

PHP代码 必须 使用 <?php ?>长标签 或 <?= ?> 短输出标签; 一定不可 使用其它自定义标签。

2.2. 字符编码

PHP代码 必须 且只可使用 不带BOMUTF-8 编码。

2.3. 副作用

一份 PHP 文件中 应该 要不就只定义新的声明,如类、函数或常量等不产生 副作用 的操作,要不就只书写会产生 副作用 的逻辑操作,但 不该 同时具有两者。

「副作用」(side effects) 一词的意思是,仅仅通过包含文件,不直接声明类、函数和常量等,而执行的逻辑操作。

「副作用」包含却不仅限于:

  • 生成输出
  • 直接的 requireinclude
  • 连接外部服务
  • 修改 ini 配置
  • 抛出错误或异常
  • 修改全局或静态变量
  • 读或写文件等

以下是一个 反例,一份包含「函数声明」以及产生「副作用」的代码:

下面是一个范例,一份只包含声明不产生「副作用」的代码:

3. 命名空间和类

命名空间以及类的命名必须遵循 PSR-4。

根据规范,每个类都独立为一个文件,且命名空间至少有一个层次:顶级的组织名称(vendor name)。

类的命名 必须 遵循 StudlyCaps 大写开头的驼峰命名规范。

PHP 5.3 及以后版本的代码 必须 使用正式的命名空间。

例如:

5.2.x 及之前的版本 应该 使用伪命名空间的写法,约定俗成使用顶级的组织名称(vendor name)如 Vendor_ 为类前缀。

4. 类的常量、属性和方法

此处的「类」指代所有的类、接口以及可复用代码块(traits)。

4.1. 常量

类的常量中所有字母都 必须 大写,词间以下划线分隔。

参照以下代码:

4.2. 属性

类的属性命名 可以 遵循:

  • 大写开头的驼峰式 ( $StudlyCaps)
  • 小写开头的驼峰式 ( $camelCase)
  • 下划线分隔式 ( $under_score)

本规范不做强制要求,但无论遵循哪种命名方式,都 应该 在一定的范围内保持一致。这个范围可以是整个团队、整个包、整个类或整个方法。

4.3. 方法

方法名称 必须 符合 camelCase() 式的小写开头驼峰命名规范。

人生的悲剧只有两种:一种是没有得到自己想要的东西,另一种是得到自己想要的东西。
                                —王尔德

标签

打赏