热门标签 | HotTags
当前位置:  开发笔记 > 后端 > 正文

数据库Schema的优化_MySQL

由于MySQL数据库是基于行(Row)存储的数据库,而数据库操作IO的时候是以page(block)的方式,也就是说,如果我们每条记录所占用的空间量减小,就会使每个page中可存放的数据行数增大,那么每次IO可
由于MySQL数据库是基于行(Row)存储的数据库,而数据库操作 IO 的时候是以 page(block)的方式,也就是说,如果我们每条记录所占用的空间量减小,就会使每个page中可存放的数据行数增大,那么每次 IO 可访问的行数也就增多了。反过来说,处理相同行数的数据,需要访问的 page 就会减少,也就是 IO 操作次数降低,直接提升性能。此外,由于我们的内存是有限的,增加每个page中存放的数据行数,就等于增加每个内存块的缓存数据量,同时还会提升内存换中数据命中的几率,也就是缓存命中率。

选择优化的数据类型

MySQL支持很多种不同的数据类型,并且选择正确的数据类型对于获得高性能至关重要。不管选择何种类型,下面的简单原则都会有助于做出更好的选择:

1、越简单越小越好

更小的数据类型通常更快,因为它们使用了更少的磁盘空间、内存和CPU缓存,而且需要的CPU周期也更少。越简单的数据类型,需要的CPU周期就越少。例如:比较整数的代价小于比较字符,因为字符集和排序规则使字符比较更复杂。

2、避免空(NULL)

要尽可地把字段定义为NOT NULL ,如果计划对列进行索引,就要尽量避免把它设置为可为空(NULL),NULL 类型比较特殊,SQL 难优化。虽然 MySQL NULL类型和 Oracle 的NULL 有差异,会进入索引中,但如果是一个组合索引,那么这个NULL 类型的字段会极大影响整个索引的效率。此外,NULL 在索引中的处理也是特殊的,也会占用额外的存放空间。很多人觉得 NULL 会节省一些空间,所以尽量让NULL来达到节省IO的目的,但是大部分时候这会适得其反,虽然空间上可能确实有一定节省,倒是带来了很多其他的优化问题,不但没有将IO量省下来,反而加大了SQL的IO量。所以尽量确保 DEFAULT 值NOT NULL,也是一个很好的表结构设计优化习惯。

难以优化了使用了可空列的查询,它会使索引、索引统计和值更加复杂

可空列需要更多的存储空间,还需要在内部进行特殊处理

当可空列被索引的时候,每条记录都需要一个额外的字节,还能导致MyISAM中固定大小的索引(例如:一个整数列上的索引)变成可变大小的索引

把NULL列改为NOT NULL 带来的性能提升很小,所以除非确定它引入了问题,否则就不要把它当成优先的优化措施

即使要在表中存储可为空的字段,也是有办法不使用NULL的,可以考虑使用0,特殊值或字符串来代替它

3、尽可能不要直接 SELECT * 读取全部字段,尤其是表中存在 TEXT/BLOB 大列的时候。可能本来不需要读取这些列,但因为偷懒写成 SELECT * 导致内存buffer pool被这些“垃圾”数据把真正需要缓冲起来的热点数据给洗出去了

4、超过20个长度的字符串列,最好创建前缀索引而非整列索引(例如:ALTER TABLE t1 ADD INDEX(user(20))),可以有效提高索引利用率,不过它的缺点是对这个列排序时用不到前缀索引。前缀索引的长度可以基于对该字段的统计得出,一般略大于平均长度一点就可以了

5、定期用 pt-duplicate-key-checker 工具检查并删除重复的索引。比如 index idx1(a, b) 索引已经涵盖了 index idx2(a),就可以删除 idx2 索引了

6、有多字段联合索引时,WHERE中过滤条件的字段顺序无需和索引一致,但如果有排序、分组则就必须一致了

7、默认地,使用InnoDB引擎,InnoDB必须要有自增(或类似自增)属性的主键

8、若无特殊需求,均可使用latin1字符集,否则用utf8\utf8mb4等大字符集保证通用性

9、基数小的不适合建立索引,复合索引比普通索引更合适

数据库类型

"TINYINT"=>1,
"SMALLINT"=>2,
"MEDIUMINT"=>3,
"INT"=>4,
"BIGINT"=>8,
"FLOAT"=>&#39;if ($M <= 24) {return 4;} else {return 8;}&#39;,
"DOUBLE"=>8,
"DECIMAL"=>&#39;if ($M <$D) {return $D + 2;} elsif ($D > 0) {return $M + 2;} else {return $M + 1;}&#39;,
"NUMERIC"=>&#39;if ($M <$D) {return $D + 2;} elsif ($D > 0) {return $M + 2;} else {return $M + 1;}&#39;,
"DATE"=>3,
"DATETIME"=>8,
"TIMESTAMP"=>4,
"TIME"=>3,
"YEAR"=>1,
"CHAR"=>&#39;$M&#39;,
"VARCHAR"=>&#39;$M+1&#39;,
"TINYBLOB"=>&#39;$M+1&#39;,
"TINYTEXT"=>&#39;$M+1&#39;,
"BLOB"=>&#39;$M+2&#39;,
"TEXT"=>&#39;$M+2&#39;,
"MEDIUMBLOB"=>&#39;$M+3&#39;,
"MEDIUMTEXT"=>&#39;$M+3&#39;,
"LONGBLOB"=>&#39;$M+4&#39;,
"LONGTEXT"=>&#39;$M+4&#39;

1、数字类型

整数

如果存储整数,就可以使用这几种整数类型,如下所示

还有一个,BIT(M) approximately (M+7)/8 bytes

SIGNED 和 UNSIGNED 占用的存储空间是一样的,性能也一样,如果确定没有负数,那就是采用UNSIGNED吧,比如作为主键的ID

整数运算通常使用64位的BINGINT整数

你可以对整数类型定义宽度,比如INT(11),这对于大在多数应用程序是没有意义的,它不限制值的范围,只规定了的交互工具(例如命令客户端)用来显示字符的个数,对于存储计算,INT(1)和INT(20)是一样的

用INT UNSIGNED 存储IPV4地址,用INET_ATON()、INET_NTOA()进行转换,基本上没必要使用CHAR(15)来存储,或者使用程序转换之后存入数据库,因为IP地址本身就是32大小位数字

实数

实数有分数部分,然而,它们并不仅仅是分数,可以使用DECIMAL保存比出BIGINT还大的整数

同时支持精确与非精确类型

FLOAT和DOUBLE类型支持使用标准的浮点运算进行近似计算

比较起DECIMAL类型,浮点类型保存同样大小的值使用的空间通常更小,而且精度更大,范围更广,和整数一样,你选择的仅仅是存储类型

MYSQL在内部对浮点类型使用DOUBLE进行计算

由于需要额外的空间和计算开销,只有在需要对小数进行精确的时候才使用DECIMAL,比如保存金融数据

一般不要使用DOUBLE,不仅仅只是存储长度的问题,同时还会存在精确性的问题。同样,固定精度的小数,也不建议使用DECIMAL,建议乘以固定倍数转换成整数存储,可以大大节省存储空间,且不会带来任何附加维护成本

2、字符串类型

VARCHAR和CHAR

varchar:保存了可变长度的字符串,是使用得最多的字符串类型,它能比固定类型占用更少的存储空间,因为它只占用了自已需要的空间(也就是说较短的值占用的空间更小)。它使用额外的1-2个字节来存储值的长度。varchar能节约空间,所以对性能有帮助。然而,由于行的长度是可变的,它们在更新的时候可能会发生变化,这会引起额外的工作。当最大长度远大于平均长度,并且很少发生更新的时候,通常适合用varchar。这时候碎片就不会成为问题,还有你使用复杂的字符集,如utf-8时,它的每个字符都可能会占用不同的存储空间。varchar存取值时候,MySQL不会去掉字符串末尾的空格。

char:固定长度,char存取值时候,MySQL会去掉末尾的空格。Char在存储很短的字符串或长度近似相同的字符的时候很有用。例如,char适用于存储密码的MD5哈希值,它的长度总是一样的。对于经常改变的值,char也好于varchar,因为固定长度的行不容易产生碎片,对于很短的列,char的效率也高于varchar。Char(1)字符串对于单字节字符集只会占用1个字节,而varchar(1)则会占用2个字节,因为有一个字节用来存储其长度。

Char和varchar的兄弟类型为binary和varbinary,它们用于保存二进制的字符串,二进制字符串的传统的字符串很类似,但是它们保存的是字节而不是字符。填充也有所不同,MySQL使用\0(0字节)填充binary值,而不是空格,并且不会在获取数据的时候把填充的值截掉。

使用varchar(5)和varchar(200)保存“hello”占用的空间是一样的,但是使用较短的列有很大的优势,较大的列会使用更多的内存,因为MySQL通常会分配固定大小的内存块来保存值。这对排序或使用基于内存的临时表尤其不好。同样的事情也会发生在使用文件排序或基于磁盘的临时表的时候。

一般不要使用 TEXT 数据类型,其处理方式决定了他的性能要低于char或者是varchar类型的处理。定长字段,建议使用 CHAR 类型,不定长字段尽量使用 VARCHAR,且仅仅设定适当的最大长度,而不是非常随意的给一个很大的最大长度限定,因为不同的长度范围,MySQL也会有不一样的存储处理

BLOB和TEXT

BLOB和TEXT分别用二进制和字符形式保存大量数据。

事实在,它们各有自的数据类型家族:

字符类型有TINYTEXT, SMALLTEXT, TEXT, MEDIUMTEXT和LONGTEXT,

二进制类型有TINYBLOB, SMALLBLOB, BLOB, MEDICMBLOB, LONGBLOB,BLOB 等同于SMALLBLOB, TEXT等同于SMALLTEXT

和其它类型不同,MYSQL把BLOB, TEXT当成有实体的对象来处理,存储引擎通常会特别地保存它们。INNODB在它们较大的时候会使用单独的“外部”存储来进行保存,每个值在行里面都需要1-4字节,并且还需要足够的外部存储空间来保存实际的值。

BLOB和TEXT唯一的区别就是BLOB保存的是二进制数据,没有字符集和排序规则,TEXT保存的是字符数据,有字符集和排序规则。

MYSQL对BLOB、TEXT列的排序方式和其它类型不同,它不会按照字符串的长度进行排序,而只是按照MAX_SORT_LENGTH规定的前若干个字节进行排序,如果只按照开始的几个字符排序,就可以减少MAX_SORT_LENGTH的值或使用ORDER BY SUBSTRING(COLUMN, LENGTH)。MYSQL不能索引这些数据类型的完整长度,也不能为排序而使用索引。

强烈反对在数据库中存放 BLOB 类型数据,虽然数据库提供了这样的功能,但这不是他所擅长的,我们更应该让合适的工具做他擅长的事情,才能将其发挥到极致

ENUM和SET

ENUM的内部存储机制是采用TINYINT或SMALLINT(并非CHAR/VARCHAR),而且即使需要增加新的类型,只要增加于末尾,修改结构也不需要重建表数据,ENUM列可以存储65535个不同的字符串,MYSQL以非常紧凑的方式保存了它们,根据列表中值的数量,MYSQL会把它们压缩到1-2个字节中,MYSQL在内部会把每个值都保存为整数,以表示值在列表中的位置,并且还保留了一份“查找表”来表示整数和字符串在表的.FRM文件中的映射关系。

ENUM最不好的一面是字符串是固定的,如果需要添加或者删除字符串必须使用ALTER TABLE,因此,对于一系列未知可能会改变的字符串,使用ENUM就不是一个好主意,MYSQL在内部的权限表中使用ENUM来保存Y值和N值。

由于MYSQL把每个值保存为整数,并且须进行查找才能把它转换成字符串形式,所以ENUM有一些开销。这通常可以由它们较小的大小进行弥补,但不总是这样,在特定情况下,把CHAR或VARCHAR列和ENUM列进行联接,可能会比联接另一个CHARA或VARCHAR列慢。

对于确定属性的字段,可以尝试使用SET类型,即使存在多种属性,同样可以游刃有余,可以结合FIND_IN_SET来使用,记住千万别用CHAR/VARCHAR来存储枚举数据

3、日期和时间类型

For example, TIME(0), TIME(2), TIME(4), and TIME(6) use 3, 4, 5, and 6 bytes, respectively. TIME andTIME(0) are equivalent and require the same storage

可以使用多种类型来保存各种日期和时间值,比中year和date,MySQL能存储的最细的时间粒度是秒,然而它可以用毫秒的粒度进行暂时的运算。

有几种相似的数据类型:DATETIME、TIMESTAMP和INT

DATETIME:范围是:1000-01-01 00:00:00 到 9999-12-31 23:59:59,与时区无关。使用了8个字节存储空间,可以使用NOW()变量来自动插入系统的当前时间

TIMESTAMP:保持了自1970年1月1日午夜(格林尼治标准时间)以来的秒数,和UNIX的时间戳相同。只使用了4个字节存储空间。可以用UNIX_TIMESTAMP()函数把日期转换为UNIX时间戳。显示的值依赖于时区,MYSQL服务器、操作系统及客户端连接都有时区设置。因此,保存0值的TIMESTAMP实际显示的时间是美国东部的时间1969-12-31 19:00:00,与格林尼治标准时间(GMT)相差5小时。最后,TIMESTAMP默认是NOT NULL,这也和其它的数据类型不一样

INT:占用4个字节;建立索引之后,查询速度快;条件范围搜索可以使用使用BETWEEN;可以使用FORM_UNIXTIME进行格式化

结论

INT适合需要进行大量时间范围查询的数据表

DATETIME类型适合用来记录数据的原始的创建时间,因为无论你怎么更改记录中其他字段的值,datetime字段的值都不会改变,除非你手动更改它

TIMESTAMP类型适合用来记录数据的最后修改时间,因为只要你更改了记录中其他字段的值,timestamp字段的值都会被自动更新

4、选择标识符

为标识列选择好的数据类型非常重要,你可能会更多地用它们和其他列做比较,还可能把它们用作其它表的外键,因为选择标识符列选择数据类型的时候,你也可能是在为相关的表选择数据类型。

当为标识符列选择数据类型的时候,不仅要考虑存储类型,还要考虑MYSQL如何对它们进行计算和比较。例如:MYSQL会在内部把ENUM和SET类型保存为整数,但是在比较的时候把它们转换为字符串。

一旦选择了数据类型,要确保在相关表中使用同样的类型。类型之前要精确匹配,包括诸如UNSIGNED这样的属性。混合不同的数据类型会导致性能问题,即使没有性能问题,隐式的类型转换也能导致难以察觉的错误,在你已经忘记了自己是在对不同类型做比较的时候,这些错误就会突然出现。

选择最小的数据类型能表明所需值的范围,并且为将来留出增长的空间。例如,如果用PORVINCE_ID来表示中国的省份,那么我们知道它不会产成千上万个值,因类就没有必要使用INT,用TINYINT就足够了,它比INT小3个节字,如果把一个表的主键是TINYINT,而另一个表以INT作为外键,那么就会造成较大的性能差距。

整数通常是标识符的最佳选择,因为它速度快,并且能使用AUTO_INCREMENT。

ENUM和SET通常不合适用作标识符,尽管它适合用来做静态的,包含了状态和“类型”和值的“定义表”。

ENUM和SET列适合用来性别、国家、省份这些固定不变的信息。

要尽可能的避免使用字符串来做标识符,因为它们占用了很多空间并且通常比整数类型要慢,特别注意不要在MYISAM表上使用字符串标识符。MYISAM默认情况下为字符串使用了压缩索引,这使查找更为缓慢。

MYISAM使用前缀压缩来减小索引大小,默认情况下会压缩字符串,也可以压缩整数

可以使用CREATE TABLE时用PACK_KEYS控制索引压缩的方式。

PACK_KEYS在MYSQL手册中如下描述:

如果您希望索引更小,则把此选项设置为1。这样做通常使更新速度变慢,同时阅读速度加快。把选项设置为0可以取消所有的关键字压缩。把此选项设置为DEFAULT时,存储引擎只压缩长的CHAR或VARCHAR列(仅限于MYISAM)。

如果您不使用PACK_KEYS,则默认操作是只压缩字符串,但不压缩数字。如果您使用PACK_KEYS=1,则对数字也进行压缩。

5、特殊类型的数据

一些数据类型没有直接对应的内建数据类型,精度低于秒的时间戳就是一个例子,另一个例子就是IP地址,人们通常使用varchar(15)来保存IP地址。但是,IP地址实际上是无符号的32位整数,而不是字符串。使用小数点来进行分纯粹是为了增加它的可读性。在实际使用时应用用无符号整数来存储IP地址。MySQL提供了INET_ATON()和INET_NTOA()函数在IP地址和整数之前转换。

数据库操作中最为耗时的操作就是 IO 处理,大部分数据库操作 90% 以上的时间都花在了 IO 读写上面。所以尽可能减少 IO 读写量,可以在很大程度上提高数据库操作的性能。

我们无法改变数据库中需要存储的数据,但是我们可以在这些数据的存储方式方面花一些心思。下面的这些关于字段类型的优化建议主要适用于记录条数较多,数据量较大的场景,因为精细化的数据类型设置可能带来维护成本的提高,过度优化也可能会带来其他的问题

字符编码

字符集直接决定了数据在MySQL中的存储编码方式,由于同样的内容使用不同字符集表示所占用的空间大小会有较大的差异,所以通过使用合适的字符集,可以帮助我们尽可能减少数据量,进而减少IO操作次数。

纯拉丁字符能表示的内容,没必要选择 latin1 之外的其他字符编码,因为这会节省大量的存储空间

如果我们可以确定不需要存放多种语言,就没必要非得使用UTF8或者其他UNICODE字符类型,这回造成大量的存储空间浪费

MySQL的数据类型可以精确到字段,所以当我们需要大型数据库中存放多字节数据的时候,可以通过对不同表不同字段使用不同的数据类型来较大程度减小数据存储量,进而降低 IO 操作次数并提高缓存命中率

适当拆分

有些时候,我们可能会希望将一个完整的对象对应于一张数据库表,这对于应用程序开发来说是很有好的,但是有些时候可能会在性能上带来较大的问题。

当我们的表中存在类似于 TEXT 或者是很大的 VARCHAR类型的大字段的时候,如果我们大部分访问这张表的时候都不需要这个字段,我们就该义无反顾的将其拆分到另外的独立表中,以减少常用数据所占用的存储空间。这样做的一个明显好处就是每个数据块中可以存储的数据条数可以大大增加,既减少物理 IO 次数,也能大大提高内存中的缓存命中率。

上面几点的优化都是为了减少每条记录的存储空间大小,让每个数据库中能够存储更多的记录条数,以达到减少 IO 操作次数,提高缓存命中率。下面这个优化建议可能很多开发人员都会觉得不太理解,因为这是典型的反范式设计,而且也和上面的几点优化建议的目标相违背。

适度冗余

为什么我们要冗余?这不是增加了每条数据的大小,减少了每个数据块可存放记录条数吗?

确实,这样做是会增大每条记录的大小,降低每条记录中可存放数据的条数,但是在有些场景下我们仍然还是不得不这样做:

被频繁引用且只能通过 Join 2张(或者更多)大表的方式才能得到的独立小字段

这样的场景由于每次Join仅仅只是为了取得某个小字段的值,Join到的记录又大,会造成大量不必要的 IO,完全可以通过空间换取时间的方式来优化。不过,冗余的同时需要确保数据的一致性不会遭到破坏,确保更新的同时冗余字段也被更新

推荐阅读
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 基于PgpoolII的PostgreSQL集群安装与配置教程
    本文介绍了基于PgpoolII的PostgreSQL集群的安装与配置教程。Pgpool-II是一个位于PostgreSQL服务器和PostgreSQL数据库客户端之间的中间件,提供了连接池、复制、负载均衡、缓存、看门狗、限制链接等功能,可以用于搭建高可用的PostgreSQL集群。文章详细介绍了通过yum安装Pgpool-II的步骤,并提供了相关的官方参考地址。 ... [详细]
  • 本文详细介绍了SQL日志收缩的方法,包括截断日志和删除不需要的旧日志记录。通过备份日志和使用DBCC SHRINKFILE命令可以实现日志的收缩。同时,还介绍了截断日志的原理和注意事项,包括不能截断事务日志的活动部分和MinLSN的确定方法。通过本文的方法,可以有效减小逻辑日志的大小,提高数据库的性能。 ... [详细]
  • 推荐一个ASP的内容管理框架(ASP Nuke)的优势和适用场景
    本文推荐了一个ASP的内容管理框架ASP Nuke,并介绍了其主要功能和特点。ASP Nuke支持文章新闻管理、投票、论坛等主要内容,并可以自定义模块。最新版本为0.8,虽然目前仍处于Alpha状态,但作者表示会继续更新完善。文章还分析了使用ASP的原因,包括ASP相对较小、易于部署和较简单等优势,适用于建立门户、网站的组织和小公司等场景。 ... [详细]
  • 搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的详细步骤
    本文详细介绍了搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的步骤,包括环境说明、相关软件下载的地址以及所需的插件下载地址。 ... [详细]
  • 本文介绍了如何在MySQL中将零值替换为先前的非零值的方法,包括使用内联查询和更新查询。同时还提供了选择正确值的方法。 ... [详细]
  • PHP设置MySQL字符集的方法及使用mysqli_set_charset函数
    本文介绍了PHP设置MySQL字符集的方法,详细介绍了使用mysqli_set_charset函数来规定与数据库服务器进行数据传送时要使用的字符集。通过示例代码演示了如何设置默认客户端字符集。 ... [详细]
  • 在数据分析工作中,我们通常会遇到这样的问题,一个业务部门由若干业务组构成,需要筛选出每个业务组里业绩前N名的业务员。这其实是一个分组排序的 ... [详细]
  • 本文介绍了在Hibernate配置lazy=false时无法加载数据的问题,通过采用OpenSessionInView模式和修改数据库服务器版本解决了该问题。详细描述了问题的出现和解决过程,包括运行环境和数据库的配置信息。 ... [详细]
  • Oracle Database 10g许可授予信息及高级功能详解
    本文介绍了Oracle Database 10g许可授予信息及其中的高级功能,包括数据库优化数据包、SQL访问指导、SQL优化指导、SQL优化集和重组对象。同时提供了详细说明,指导用户在Oracle Database 10g中如何使用这些功能。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
author-avatar
mobiledu2502881573
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有