十九、PHP 安全编程
任何暴露在互联网上的网站或服务都可以被认为是一座不断受到野蛮人攻击的城堡。正如传统战争和信息战的历史所显示的,攻击者的胜利并不完全取决于他们的技术或狡猾程度,而是取决于城堡防御中的一个疏忽。作为电子王国的守护者,您面临着大量可能造成破坏的潜在入侵,尤其包括:
-
软件漏洞 : Web 应用由多种技术构建而成,通常是数据库服务器、Web 服务器和一种或多种编程语言——所有这些都运行在一个或多个操作系统上。因此,在攻击者利用问题之前,不断了解并解决所有任务关键型技术中新发现的漏洞至关重要。确保您的所有软件都安装了最新的安全补丁。这适用于操作系统以及用于网站或服务的软件堆栈。在许多情况下,该软件依赖于其他包中的库和功能,即使您的站点没有使用这些库和功能。
-
用户输入:利用因用户输入处理不当而产生的漏洞可能是对您的数据和应用造成严重损害的最简单方式,这一论断得到了无数此类成功攻击报告的支持。对通过 HTML 表单、URL 参数、cookies 和其他容易访问的途径传递的数据的操纵,使攻击者能够攻击应用逻辑的核心。这可能是网站中开发者控制力最强的部分。开发人员有责任以消除安全漏洞的方式编写代码。永远不要相信任何对你的网站或服务的输入。它被暴露在互联网上,任何了解它的人都可以使用他/她所拥有的任何工具来尝试访问数据或注入恶意代码。
-
保护不力 数据:数据是你公司的命脉;丢失风险自担。然而,由于容易识别的 URL,数据库帐户常常受到可疑密码的保护,或者基于 web 的管理控制台大开方便之门。这些类型的安全失误是不可接受的,尤其是因为它们很容易解决。
因为每种情况都对您的应用的完整性构成了巨大的风险,所以必须对所有情况进行彻底的调查并做出相应的处理。这一章回顾了你可以采取的许多步骤来对冲——甚至消除——这些危险。
小费
验证和净化用户输入是一个非常严肃的问题,所以我不想等到本版的第十九章来讨论这个话题。因此,关于处理用户输入的重要信息已被移至第十三章。如果你还没有仔细阅读这些材料,我强烈建议你现在就阅读。
安全地配置 PHP
PHP 提供了许多配置参数,旨在大大提高其安全意识水平。本节介绍了许多最相关的选项。
注意
多年来,PHP 提供了一个被称为安全模式的特定于安全的选项,它试图通过限制对 PHP 的许多本机特性和功能的访问来使 PHP 和 web 服务器更加安全。然而,由于安全模式解决的问题越多,产生的问题也越多,很大程度上是因为企业应用需要使用安全模式禁用的许多特性,所以开发人员决定从 PHP 5.3.0 开始弃用该特性。因此,尽管你会在网上找到很多关于安全模式的参考,你应该避免使用它,而是寻求实现其他的安全措施(其中很多在本章中介绍)。
与安全相关的配置参数
本节介绍几个配置参数,它们在更好地保护 PHP 安装中起着重要作用。在你开始深入这一部分之前,你应该考虑你的网站或服务的托管环境。如果您在一个共享环境中,您可能对 PHP 配置有有限的控制,并且您将与同一主机的其他用户共享可用资源。如果另一个网站使用了所有的磁盘空间或内存,您的网站可能会停止工作或变得不稳定。我建议使用专用的托管环境,如虚拟专用服务器(VPS)或专用硬件。
disable_functions = 字符串
范围:PHP_INI_SYSTEM
;默认值:NULL
您可以将disable_functions
设置为您想要禁用的函数名的逗号分隔列表。假设您只想禁用fopen()
, popen()
,
和file()
功能。如下设置该指令:
disable_functions = fopen,popen,file
这个选项通常用在共享主机环境中,主机提供商希望限制每个 PHP 开发人员可以访问的功能。在允许多个开发人员为同一个站点或服务编写代码的环境中,它也很有用。
disable_classes = 字符串
范围:PHP_INI_SYSTEM
;默认值:NULL
考虑到 PHP 对面向对象范式的接受所提供的新功能,用不了多久,您就可以使用大型类库了。然而,在这些库中可能有一些你不愿意使用的类。您可以使用disable_classes
指令来阻止这些类的使用。例如,您可以完全禁用两个名为administrator
和janitor
的类:
disable_classes = "administrator, janitor"
display_errors = 开 | 关
范围:PHP_INI_ALL
;默认值:On
在开发应用时,如果脚本执行过程中出现任何错误,立即得到通知是非常有用的。PHP 将通过向浏览器窗口输出错误信息来满足这一需求。但是,这些信息可能会被用来揭示有关您的服务器配置或应用的具有潜在破坏性的详细信息。当应用转移到生产环境时,请记住禁用此指令。当然,您可以通过将这些错误消息保存到日志文件或使用其他日志机制来继续查看它们。参见第八章了解更多关于 PHP 日志特性的信息。
max_execution_time = 整数
范围:PHP_INI_ALL
;默认值:30
这不是一个安全设置,而是一种控制脚本使用资源的方式。该指令指定脚本在终止前可以执行的秒数。这有助于防止用户的脚本消耗过多的 CPU 时间。如果max_execution_time
设置为0
,则不设置时间限制。在 PHP 的 CLI 版本中,这默认为 0,即使在 php.ini 中定义了另一个值。
内存限制= 内存
范围:PHP_INI_ALL
;默认值:128M
同样,这不是一个与安全相关的选项,而是用于限制脚本可以使用的资源量。这个指令以兆字节为单位指定一个脚本可以使用多少内存。请注意,您不能用兆字节以外的术语来指定这个值,并且您必须始终在数字后面加上一个M
。该指令仅适用于在配置 PHP 时启用了--enable-memory-limit
的情况。
open _ base dir =字符串
范围:PHP_INI_ALL
;默认值:NULL
PHP 的open_basedir
指令可以建立一个基本目录,所有的文件操作都将被限制在这个目录中,就像 Apache 的DocumentRoot
指令一样。这可以防止用户进入服务器的其他受限区域。例如,假设所有 web 资料都位于目录/home/www
中。为了防止用户通过几个简单的 PHP 命令查看和潜在操纵文件,比如/etc/passwd
,可以考虑这样设置open_basedir
:
open_basedir = "/home/www/"
user_dir = 字符串
范围:PHP_INI_SYSTEM
;默认值:NULL
该指令指定用户主目录中的目录名,PHP 脚本必须放在该目录中才能执行。例如,如果user_dir
被设置为脚本,用户 Johnny 想要执行somescript.php
,Johnny 必须在其主目录中创建一个名为scripts
的目录,并将somescript.php
放入其中。然后可以通过 URL http://example.com/~johnny/scripts/somescript.php
访问这个脚本。该指令通常与 Apache 的UserDir
配置指令一起使用。
隐藏配置详细信息
许多程序员更喜欢把他们部署开源软件的决定作为一个徽章让全世界看到。然而,重要的是要认识到,您发布的关于您的项目的每一条信息都可能为攻击者提供重要的线索,这些线索最终可能被用来渗透您的服务器。考虑另一种方法,让您的应用保持独立,同时尽可能对技术细节保持沉默。尽管混淆只是整个安全画面的一部分,但它仍然是一种应该永远记住的策略。请记住,心怀不轨的人可以访问开源库的源代码,这使他们能够找到漏洞。
隐藏阿帕奇人
Apache 输出包含在所有文档请求和服务器生成的文档中的服务器签名(例如,500 内部服务器错误文档)。两个配置指令负责控制这个签名:ServerSignature
和ServerTokens
。
Apache 的服务器签名指令
ServerSignature
指令负责插入与 Apache 的服务器版本、服务器名称(通过ServerName
指令设置)、端口和编译模块相关的单行输出。当启用并与ServerTokens
指令(接下来介绍)一起工作时,它能够显示如下输出:
Apache/2.4.18 (Ubuntu) Server at localhost Port 80
很可能你宁愿把这些信息留给自己。因此,考虑通过将其设置为Off
来禁用该指令。
如果ServerSignature
被禁用,则该指令没有实际意义。如果由于某种原因必须启用ServerSignature
,考虑将指令设置为Prod
。
Apache 的 ServerTokens 指令
如果启用了ServerSignature
指令,则ServerTokens
指令确定提供何种程度的服务器细节。有六种选择:Full
、Major
、Minimal
、Minor
、OS
和Prod
。表 21-1 中给出了各自的示例。
表 21-1
ServerTokens 指令的选项
|[计]选项
|
例子
|
| — | — |
| Full
| Apache/2.4.18 (Ubuntu) PHP/7.2.1 服务器 |
| Major
| Apache/2 服务器 |
| Minimal
| Apache/2.4.18 服务器 |
| Minor
| Apache/2.4 服务器 |
| OS
| Apache/2.4.18 (Ubuntu)服务器 |
| Prod
| Apache 服务器 |
隐藏 PHP
您可以掩盖 PHP 正在您的服务器上使用的事实。使用expose_php
指令来防止 PHP 版本细节被附加到您的 web 服务器签名中。阻止对phpinfo()
的访问可以防止攻击者了解你的软件版本号和其他关键信息。更改文档扩展名,使页面映射到 PHP 脚本不那么明显。
expose _ PHP =1 | 0
范围:PHP_INI_SYSTEM
;默认值:1
启用时,PHP 指令expose_php
将其细节附加到服务器签名中。例如,如果ServerSignature
被启用,ServerTokens
被设置为Full
,并且该指令被启用,则服务器签名的相关组件将如下所示:
Apache/2.4.18 (Ubuntu) PHP/7.2.1 Server
当expose_php
被禁用时,服务器签名将如下所示:
Apache/2.4.18 (Ubuntu) Server
移除 phpinfo()调用的所有实例
phpinfo()
函数为查看给定服务器上 PHP 的配置摘要提供了一个很好的工具。然而,如果在服务器上不受保护,它提供的信息对攻击者来说就是一座金矿。例如,这个函数提供关于操作系统、PHP 和 web 服务器版本、配置标志的信息,以及关于所有可用扩展及其版本的详细报告。让攻击者能够访问这些信息将极大地增加潜在攻击媒介被发现并随后被利用的可能性。
不幸的是,似乎许多开发人员没有意识到或不关心这样的披露。事实上,在搜索引擎中键入phpinfo.php会产生超过 400,000 个结果,其中许多直接指向执行phpinfo()
命令的文件,因此提供了大量关于服务器的信息。快速细化搜索条件以包括其他关键字会产生初始结果的子集(旧的、易受攻击的 PHP 版本),这些结果可能成为攻击的主要目标,因为它们使用了已知的不安全版本的 PHP、Apache、IIS 和各种支持的扩展。
允许其他人查看来自phpinfo()
的结果实质上相当于为公众提供了一个路线图,让他们了解你的服务器的许多技术特点和缺点。不要仅仅因为懒得删除或保护这个文件而成为攻击的受害者。使用disable_functions
指令在生产环境中禁用该功能是一个好主意。
更改文档扩展名
支持 PHP 的文档很容易通过它们独特的扩展名来识别,最常见的是.php
、.php3
和.phtml
。你知道吗,这可以很容易地改变成你想要的任何其他扩展名,甚至是.html
、.asp
或.jsp
?只需更改您的httpd.conf
文件中的行,内容如下
AddType application/x-httpd-php .php
您可以随意扩展,例如
AddType application/x-httpd-php .asp
当然,您需要确保这不会导致与其他已安装的服务器技术或开发环境的冲突。或者,您也可以使用 web 服务器的 URL 重写功能来创建没有文件扩展名的更友好的 URL。
隐藏敏感数据
位于服务器文档树中并拥有足够权限的任何文档都是任何能够执行GET
命令的机制的合理检索对象,即使它不是从另一个网页链接的,或者不以 web 服务器识别的扩展名结尾。不信服?作为练习,创建一个文件,在这个文件中输入我的秘密。将这个文件保存到你的公共 HTML 目录下,命名为秘密,使用一些奇怪的扩展名,比如.zkgjg
。显然,服务器不会识别这个扩展名,但它会尝试检索数据。现在转到您的浏览器,使用指向该文件的 URL 请求该文件。很可怕,不是吗?
当然,用户需要知道他想要检索的文件的名称。然而,就像假定包含phpinfo()
函数的文件将被命名为phpinfo.php
一样,要找到受限文件,需要一点技巧和利用 web 服务器配置缺陷的能力。幸运的是,有两种简单的方法可以彻底纠正这个问题。这个问题由于开源库的使用而变得更加严重。任何其他开发人员/黑客都可以下载相同的库,并通读代码以找到利用该库的可能方法。当发现漏洞时,很容易扫描网站以检查它们是否暴露了漏洞。
隐藏文档根
Apache 的httpd.conf
文件中有一个名为DocumentRoot
的配置指令。这被设置为您希望服务器识别为公共 HTML 目录的路径。如果没有采取其他保护措施,则在此路径中找到的任何文件,如果分配了足够的权限,都能够得到服务,即使该文件没有可识别的扩展名。但是,用户不可能查看位于此路径之外的文件。因此,考虑将您的配置文件放在DocumentRoot
路径之外。
要检索这些文件,您可以使用include()
将这些文件包含到任何 PHP 文件中。例如,假设您像这样设置DocumentRoot
:
DocumentRoot C:/apache2/htdocs # Windows
DocumentRoot /www/apache/home # Linux
假设您正在使用一个日志包,它将站点访问信息写入一系列文本文件。您肯定不希望任何人查看这些文件,所以将它们放在文档根目录之外是个好主意。因此,您可以将它们保存到先前路径之外的某个目录中:
C:/Apache/sitelogs/ # Windows
/usr/local/sitelogs/ # Linux
拒绝访问某些文件扩展名
防止用户查看某些文件的第二种方法是通过配置httpd.conf
文件Files
指令来拒绝对某些扩展名的访问。假设你不希望任何人访问扩展名为.inc
的文件。将以下内容放入您的httpd.conf
文件中:
<Files *.inc>
Order allow,deny
Deny from all
</Files>
添加之后,重新启动 Apache 服务器。您会发现,任何通过浏览器请求查看扩展名为.inc
的文件的用户都被拒绝访问。但是,您仍然可以在脚本中包含这些文件。顺便说一下,如果你搜索一下httpd.conf
文件,你会发现这是用来保护访问.htaccess
的相同前提。
数据加密
加密可以定义为将数据转换成除目标方之外的任何人都无法读取的格式。然后,目标方可以通过使用某种秘密——通常是密钥或密码——对加密数据进行解码,或解密。PHP 支持几种加密算法;比较突出的在这里描述。
PHP 的加密功能
在深入了解 PHP 的加密功能之前,有必要讨论一下它们的用法,这与解决方案无关。除非运行加密方案的脚本在支持 SSL 的服务器上运行,否则 Web 上的加密在很大程度上是无用的。为什么呢?PHP 是一种服务器端脚本语言,所以信息必须以纯文本格式发送到服务器,然后才能被加密。如果用户没有通过安全连接进行操作,当信息从用户传输到服务器时,不希望的第三方可以通过多种方式看到这些信息。过去,为 web 服务器获取 SSL 证书是有成本的。最近几年价格已经下降,甚至还有像 https://letsencrypt.org
这样的免费服务,可以让你获得有效期为三个月的 SSL 证书。他们甚至提供了一些工具来简化证书的更新。不再有任何借口不拥有使用 HTTPS 协议而不是 HTTP 的加密网站。如果您接受来自用户的任何形式的数据(用户 id、密码、信用卡信息等)。),您应该始终提供到 web 服务器的加密连接。有关设置安全 Apache 服务器的更多信息,请访问 https://httpd.apache.org/docs/2.2/ssl
。如果您使用不同的 web 服务器,请参考您的文档。对于您的特定服务器,即使没有几个安全解决方案,至少也有一个。有了这个警告,让我们回顾一下 PHP 的加密函数。
使用 hash()散列函数散列数据
hash()
函数可用于使用多种不同散列算法之一创建所谓的散列。哈希数据是一种不可逆的数据编码方式,因此它们不再可读;因为它是不可逆的,所以不可能从中产生原始价值。存储密码或创建数字签名时会使用哈希数据。如果您要验证密码或数字签名,您必须创建一个新的哈希值,并将其与存储的哈希值进行比较。反过来,数字签名可以用来唯一地识别发送方。它的原型是这样的:
string hash(string algo, string data [, bool raw_output])
支持许多不同的算法。这些有不同的复杂性。一种更简单的算法叫做 MD5。它不再被认为是安全的,不应以任何方式用于保护数据或访问网站。如今,像 sha256 或 sha512 这样的算法具有更高的复杂性,因此更难破解。
使用hash_algos()
功能可以获得支持算法的完整列表。随着新算法的开发和添加到 PHP 中,您可以使用这个函数来检查当前可用的函数。当前列表如下所示:
Array
(
[0] => md2
[1] => md4
[2] => md5
[3] => sha1
[4] => sha224
[5] => sha256
[6] => sha384
[7] => sha512/224
[8] => sha512/256
[9] => sha512
[10] => sha3-224
[11] => sha3-256
[12] => sha3-384
[13] => sha3-512
[14] => ripemd128
[15] => ripemd160
[16] => ripemd256
[17] => ripemd320
[18] => whirlpool
[19] => tiger128,3
[20] => tiger160,3
[21] => tiger192,3
[22] => tiger128,4
[23] => tiger160,4
[24] => tiger192,4
[25] => snefru
[26] => snefru256
[27] => gost
[28] => gost-crypto
[29] => adler32
[30] => crc32
[31] => crc32b
[32] => fnv132
[33] => fnv1a32
[34] => fnv164
[35] => fnv1a64
[36] => joaat
[37] => haval128,3
[38] => haval160,3
[39] => haval192,3
[40] => haval224,3
[41] => haval256,3
[42] => haval128,4
[43] => haval160,4
[44] => haval192,4
[45] => haval224,4
[46] => haval256,4
[47] => haval128,5
[48] => haval160,5
[49] => haval192,5
[50] => haval224,5
[51] => haval256,5
)
如果使用 hash()函数创建存储在数据库中的值,需要确保数据库中的列足够宽,能够容纳所用算法的值。
例如,假设你的秘密密码 toystore 有一个7518ce67ee48edc55241b4dd38285e876cb75b620930fd6e358d4b3ad74cac60
的 sha256 散列。您可以将此哈希值存储在服务器上,并将其与用户尝试输入的密码的 sha256 哈希值进行比较。即使入侵者得到了加密的密码,也不会有太大的不同,因为入侵者无法通过常规手段将字符串还原为其原始格式。下面是一个使用hash()
散列字符串的例子:
<?php
$val = "secret";
$hash_val = hash('sha256', $val);
// $hash_val = "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b";
?>
请记住,要在数据库中存储完整的 sha256 哈希,需要将字段长度设置为 64 个字符。虽然散列只有 256 bilt 长,但输出是用十六进制表示法编写的,每个字节使用两个字符。
虽然hash()
函数可以满足大多数哈希需求,但是您的项目可能需要使用另一种哈希算法。PHP 的散列扩展支持许多散列算法和变体。在 http://us3.php.net/hash
了解更多关于这个强大扩展的信息。
请注意,MD5 函数已经显示为不同的输入提供相同的哈希值。这个函数不再被认为是安全的密码哈希或创建签名,但它可以用来创建一个文件的内容哈希。然后,散列可以存储在数据库中,当为另一个文件创建散列时,很容易比较该文件是否以前见过。如果您创建了一个用户可以上传图像的站点,这将非常有用。如果同一个图像被上传了不止一次,您可以检测到这一点,并简单地引用同一个图像。
PHP 提供了一个特殊的哈希函数来处理名为password_hash()
的密码。这个函数将处理 salt 值和散列算法,对于相同的密码值,为相同的密码返回的字符串永远不会相同。为了将密码与保存的密码进行比较,您必须调用函数password_verify()
。该函数将使用与创建原始散列相同的 salt 和算法来创建密码散列,然后比较两个散列值。接下来的两个示例显示了如何创建密码哈希以及如何验证密码:
<?php
$password = "secret";
$hash = password_hash($password, PASSWORD_DEFAULT);
echo $hash;
?>
执行此示例将生成如下输出:
$2y$10$s.CM1KaHMF/ZcskgY6FRu.IMJMeoMgaG1VsV6qkMaiai/b8TQX7ES
每次运行代码,都会生成不同的输出。为了验证密码,您可以使用类似于下例的代码:
<?php
$hash = '$2y$10$s.CM1KaHMF/ZcskgY6FRu.IMJMeoMgaG1VsV6qkMaiai/b8TQX7ES';
$passwords = ["secret", "guess"];
foreach ($passwords as $password) {
if (password_verify($password, $hash)) {
echo "Password is correct\n";
}
else {
echo "Invalid Password\n";
}
}
?>
在本例中,我们测试两个不同的密码。第一个是上一个示例中用于生成哈希的密码,第二个是不正确的密码。该代码生成以下输出:
Password is correct
Invalid Password
在实际应用中,您应该将密码的哈希存储在数据库或文件中。将真实密码存储在数据库中将允许该数据库的管理员读取其他用户的密码,并且他们将能够使用该密码进行恶意操作。
使用 OpenSSL 加密数据
谈到以安全的方式存储数据,PHP 提供了一个名为 OpenSSL 的库。这个库允许您使用加密密钥加密和解密值。如果您的硬盘或数据库遭到破坏,黑客将无法读取加密内容,除非您也将加密密钥留在了硬盘上。
有两种基本类型的密钥可用于加密和解密。第一种是对称密钥,加密和解密使用相同的密钥。第二种类型使用公钥和私钥对,其中一个密钥用于加密,另一个用于解密。这可以用来在交换信息时增加额外的安全层。如果发送方使用私钥加密,然后使用接收方的公钥再次加密该值,则接收方可以使用自己的私钥,然后使用发送方的公钥进行解密。这确保了只有预期的收件人可以打开文件,并且收件人肯定知道文件来自预期的来源。
使用非对称密钥对大量文本进行加密可能需要很长时间,加密方式通常略有不同,使用对称密钥对有效负载进行加密,然后使用一个或两个非对称密钥对简短的对称密钥进行加密,加密的有效负载和加密的对称密钥都将被交换。
在下一个例子中,我们将创建一个使用对称密钥加密和解密字符串的类。这将是对openssl_encrypt()
和openssl_decrypy()
函数的包装。这两个函数都有三个强制参数(
d
a
t
a
、
data、
data、cipher 和
k
e
y
)
和五个可选参数。该示例利用了前两个可选参数
(
key)和五个可选参数。该示例利用了前两个可选参数(
key)和五个可选参数。该示例利用了前两个可选参数(options 和$iv)。
密码值用于选择要使用的加密方法。该类默认使用 AES-128-CBC。通过调用openssl_get_cipher_methods()
函数可以获得可用密码的完整列表。
i
v
参数是初始化向量,它生成为对应于所选密码长度的多个随机字节值。函数
‘
o
p
e
n
s
s
l
c
i
p
h
e
r
i
v
l
e
n
g
t
h
‘
(
)
和
‘
o
p
e
n
s
s
l
r
a
n
d
o
m
p
s
e
u
d
o
b
y
t
e
s
‘
(
)
用于获取长度和随机字节列表。重要的是,加密和解密使用相同的初始化向量,以确保发生这种情况。
iv 参数是初始化向量,它生成为对应于所选密码长度的多个随机字节值。函数`openssl_cipher_iv_length`()和`openssl_random_pseudo_bytes`()用于获取长度和随机字节列表。重要的是,加密和解密使用相同的初始化向量,以确保发生这种情况。
iv参数是初始化向量,它生成为对应于所选密码长度的多个随机字节值。函数‘opensslcipherivlength‘()和‘opensslrandompseudobytes‘()用于获取长度和随机字节列表。重要的是,加密和解密使用相同的初始化向量,以确保发生这种情况。iv 值与签名哈希一起被添加到加密字符串的前面,在解密时可以使用签名哈希来确保该值不被更改。
<?php
//
class AES {
private $key = null;
private $cipher = "AES-128-CBC";
function __construct($key, $cipher = "AES-128-CBC") {
$this->key = $key;
$this->cipher = $cipher;
}
function encrypt($data) {
if (in_array($this->cipher, openssl_get_cipher_methods())) {
$ivlen = openssl_cipher_iv_length($this->cipher);
$iv = openssl_random_pseudo_bytes($ivlen);
$encrypted = openssl_encrypt($data, $this->cipher, $this->key, OPENSSL_RAW_DATA, $iv);
$hmac = hash_hmac('sha256', $encrypted, $this->key, true);
return base64_encode($iv.$hmac.$encrypted);
}
else {
return null;
}
}
function decrypt($data) {
$c = base64_decode($data);
$ivlen = openssl_cipher_iv_length($this->cipher);
$iv = substr($c, 0, $ivlen);
$hmac = substr($c, $ivlen, $sha2len=32);
$encrypted = substr($c, $ivlen+$sha2len);
$hmac_check = hash_hmac('sha256', $encrypted, $this->key, true);
if (hash_equals($hmac, $hmac_check)) {
return openssl_decrypt($encrypted, $this->cipher, $this->key, OPENSSL_RAW_DATA, $iv);
}
else {
return null;
}
}
}
下一个例子展示了如何使用扩展的简单例子。在这个例子中,使用的密钥是一个静态的纯文本字符串,但是一个更好的密钥可以是一个字符串的散列或一个随机字节的字符串。关键是加密和解密必须使用相同的密钥。
<?php
include "./aes.inc";
$aes = new AES('My Secret Key');
$e = $aes->encrypt("This message is secure and must be encrypted");
echo "Encrypted: '$e'\n";
$d = $aes->decrypt($e);
echo "Decrypted: '$d'\n";
输出将类似于下面的清单。由于初始化向量中的随机字节,输出将随着每次执行而改变。该示例显示消息被成功解密。
Encrypted: 'Nc+Oq+exEF1ZrepYbcV6f2XL8stA1WGJy5JmLPIqTOrRGfLWMIx9roLWgGEhbQppOv3VVXGxs4PJodKh7dQsviMUW9asCXDStbEfh+4PRZTQDFer/WQ9aOjKs9DF3kKm'
Decrypted: 'This message is secure and must be encrypted'
摘要
本章提供的材料为您提供了几个重要的提示,但主要目的是让您思考您的应用和服务器面临的许多攻击媒介。请注意,本章中描述的主题只是整个安全性的一小部分。如果你是这个主题的新手,花些时间访问著名的安全相关网站。
不管您以前的经验如何,您都需要设计一个策略来跟上突发的安全新闻。从更流行的以安全为中心的网站以及产品开发人员那里订阅时事通讯可能是最好的方法。最重要的是,你要有一个策略并坚持下去,以免你的城堡被征服。
二十、集成 jQuery 和 PHP
多年来,web 开发人员抱怨无法创建复杂的、响应迅速的界面,类似于桌面应用中的界面。这一切在 2005 年开始改变,当时用户体验大师杰西·詹姆斯·加勒特创造了一个术语 Ajax 1 ,描述 Flickr 和谷歌等先进的尖端网站一直在取得进步,弥合网络界面和基于客户端的兄弟之间的差距。这些进步包括利用浏览器与服务器异步通信的能力——无需重新加载网页。结合 JavaScript 检查和操作 web 页面的几乎所有方面的能力(得益于该语言与页面的文档对象模型(也称为 DOM)进行交互的能力),创建能够执行各种任务而无需重新加载页面的界面成为可能。
在本章中,我将讨论 Ajax 的技术基础,并向您展示如何结合 PHP 使用强大的 jQuery ( https://jquery.com
)库来创建 Ajax 增强的特性。我假设您至少已经对 JavaScript 语言有了基本的了解。如果你不熟悉 JavaScript,我建议你花些时间阅读位于 https://w3schools.com/js
的优秀 JavaScript 教程。此外,因为 jQuery 是一个具有强大功能的库,所以这一章实际上只是触及了它的皮毛。请务必访问 jQuery 网站 https://www.jquery.com
获取完整的概述。
Ajax 简介
Ajax 是 Asynchronous JavaScript 和 XML 的缩写,它不是一种技术,而是一个总括术语,用来描述一种创建高度交互的 web 界面的方法,这种界面非常类似于桌面应用中的界面。这种方法包括集成多种技术,包括 JavaScript、XML、一种基于浏览器的异步通信管理机制,以及通常(尽管不是必需的)一种能够完成异步请求并返回相应响应的服务器端编程语言。如今,使用 JavaScript 对象表示法(JSON)作为交换消息的格式更加普遍。
注意
一个异步事件能够独立于主应用执行,而不会阻塞在异步事件启动时可能已经在执行的其他事件,或者可能在异步事件完成之前开始执行的其他事件。
多亏了 jQuery 等优秀的 JavaScript 库和 PHP 等语言的原生能力,许多涉及启动异步通信和有效负载构造和解析的血淋淋的细节都从开发人员那里抽象出来了。然而,理解 Ajax 请求的构建块使得在客户端和服务器端编写和调试代码变得更加容易。
尽管 Ajax 将 XML 作为名称的一部分,但它更多地用于创建和接收 JSON 格式的文本负载,XML 不再是主导格式。从服务器端来看,用户在浏览器地址栏中输入 URL 发起的请求和使用 Ajax 发出的请求没有什么区别。响应可以由静态 HTML 文件生成,也可以由 PHP 脚本生成的动态文件生成。
总之,以 Ajax 为中心的特性依赖于几种技术和数据标准才能正常工作,包括服务器端和客户端语言、DOM,以及能够被过程中涉及的所有各方理解的数据格式(通常是 JSON)。为了进一步阐明工作流程和涉及的技术,该流程如图 20-1 所示。
图 20-1
典型的 Ajax 工作流
jQuery 简介
在我看来,jQuery 是 JavaScript 的“修复”版本,纠正了许多丑陋而乏味的语法,这些语法多年来一直是 web 开发人员的祸根。由 JavaScript 大师约翰·瑞西格( https://ejohn.org
)创建的 JavaScript 库,jQuery 已经变得如此受欢迎,以至于它在世界上 10,000 个访问量最大的网站中的 76%中发挥了作用, 2 其中包括 Google、Mozilla 和 NBC。鉴于该库与 DOM 的深度集成、方便的 Ajax 助手方法、令人印象深刻的用户界面效果和可插拔架构,这并不奇怪。
jQuery 确实是猫的喵,在这一节中,我将向您介绍一些关键特性,这些特性不仅使它成为将 Ajax 特性集成到您的网站中的理想选择,而且也是执行几乎所有其他面向 JavaScript 的任务的理想选择。像 JavaScript 语言一样,jQuery 是一个如此庞大的主题,以至于它本身就足以成为一本书,所以一定要花些时间浏览 jQuery 网站 https://www.jquery.com
来了解关于这个强大的库的更多信息。
安装 jQuery
jQuery 是一个开源项目,可以从 https://www.jquery.com
免费下载。打包成一个独立的文件,像其他 JavaScript 文件一样整合到您的网站中,将它放在服务器上的公共目录中,并从网站的<head>
标记中的任意位置引用它,如下所示:
<script type="text/javascript" src="jquery-3.3.1.min.js"></script>
然而,由于 jQuery 是一个如此广泛使用的库,Google 在其内容分发网络(CDN)上托管该库,并提供一个 API,允许开发人员引用托管库,而不是维护一个单独的副本。通过引用 Google 的托管版本,您可以降低自己的带宽成本,并最终帮助您的网站加载更快,因为用户在访问另一个使用 Google CDN 的网站时,可能已经在本地缓存了一份 jQuery。使用以下代码片段从 jQuery CDN 加载 jQuery:
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
jQuery 的特定版本是 URL 的一部分。如果不想使用 3.3.1 版本(撰写本文时的最新版本),可以选择其他版本。
一个简单的例子
与原生 JavaScript 代码一样,您需要组织 jQuery 代码,确保在 HTML 页面加载到客户端浏览器之前不会执行。忽略这一点可能会导致意想不到的副作用,因为 JavaScript 可能会尝试检查或修改尚未呈现的页面元素。为了防止这种情况发生,您将把 jQuery 代码嵌入到它的ready
事件中:
<script>
$(document).ready(function() {
alert("Your page is ready!");
});
</script>
将这段代码插入加载 jQuery 库的代码之后。重新加载页面,你会看到如图 20-2 所示的警告框。
图 20-2
用 jQuery 显示警告框
此处包含 HTML 文档的完整列表以供参考。
<html>
<head>
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script>
$(document).ready(function() {
alert("Your page is ready!");
});
</script>
</head>
<body>
</body>
</html>
响应事件
尽管很有用,但是 JavaScript 的本地事件处理器很难维护,因为它们必须与相关的 HTML 元素紧密耦合。例如,通常的做法是使用如下代码将一个onClick
事件处理器与一个特定的链接相关联:
<a href="#" class="button" id="check_un" onClick="checkUsername(); return false;">Check Username Availability</a>
这是一种非常丑陋的方法,因为它与网站的设计和逻辑联系得太紧密了。jQuery 通过允许您将相关的侦听器从元素中分离出来来解决这个问题。事实上,您不仅可以通过编程将事件与特定元素相关联,还可以将它们与特定类型、id 的所有元素、分配了特定 CSS 类名的元素,甚至满足特定嵌套条件的元素相关联,例如嵌套在与类名tip
相关联的段落中的所有图像。让我们从一个最简单的例子开始,重构上面的例子,将 jQuery click
处理器与分配了 ID check_un
的页面元素关联起来:
<html>
<head>
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script>
$(document).ready(function() {
$("#check_un").click(function(event) {
alert("Checking username for availability");
event.preventDefault();
})
});
</script>
</head>
<body>
<p>Click <b id="check_un">here</b> to check if username is available</p>
</body>
</html>
$()
语法只是一个 jQuery 快捷方式,用于根据标记名、类属性和 ID 检索页面元素,也称为 CSS 选择器。在这个例子中,您正在寻找一个由 ID check_un
标识的元素,因此已经将#check_un
传递到快捷方式中。接下来,将 jQuery 的click
方法附加到元素上,使 jQuery 开始监视与该元素关联的 click 类型的事件。在随后的匿名函数中,您可以定义希望与该事件一起发生的任务,在本例中包括显示一个警告框,并使用另一个方便的 jQuery 特性来防止元素的默认行为发生(在超链接的情况下,这将是试图访问与href
属性相关联的页面)。
Id‘check _ un’被赋予单个元素,在本例中是单词 here 周围的粗体标签。单击这个单词将导致在单击处理器中定义的警告框出现,即使没有 JavaScript 明确绑定到超链接!
让我们考虑另一个例子。假设您想要将一个mouseover
事件与页面中找到的所有图像相关联,这意味着每当鼠标指针进入图像边界时就会执行该事件。要创建事件,只需将 HTML 元素的名称(img
)传递到$()
快捷方式中:
$("#check_un").mouseover(function(event){
alert("Interested in this image, are ya?");
});
如前所述,也可以只将事件与满足特定复杂条件的元素相关联,例如由 class 属性thumbnail
定义的图像,这些图像嵌套在由 ID sidebar
标识的 DIV 中:
$("#sidebar > img.thumbnail").click(function(event) {
alert("Loading image now…");
});
显然,仅仅为了显示警告框而使用 jQuery 并不是您主要关心的问题。因此,接下来让我们考虑如何使用 jQuery 以有用的方式检查和修改 DOM。通过本节的总结,您将了解如何创建事件,这些事件在被触发时可以执行任务,例如通知用户任务已完成、向表中添加行以及隐藏页面的某些部分。
jQuery 和 DOM
尽管 jQuery 有无数的附加功能,但我发现它解析和操作 DOM 的能力是它的杀手锏。在本节中,我将通过提供一系列解析和操作以下 HTML 片段的示例,向您介绍 jQuery 在这方面的能力:
<body>
<span id="title">Easy Google Maps with jQuery, PHP and MySQL</span>
<img srcimg/maps.png" class="cover" />
<p>
Author: W. Jason Gilmore<br />
Learn how to create location-based websites using popular open source technologies and the powerful Google
Maps API! Topics include:
</p>
<ul>
<li>Customizing your maps by tweaking controls, and adding markers and informational windows</li>
<li>Geocoding addresses, and managing large numbers of addresses within a database</li>
<li>How to build an active community by allowing users to contribute new locations</li>
</ul>
</body>
要检索书名,请使用以下语句:
var title = $("#title").html();
要获得与类cover
相关联的图像的src
值,请使用以下语句:
var src = $("img.cover").attr("src");
还可以检索和了解更多关于元素组的信息。例如,您可以通过使用 jQuery 的size()
方法和选择器快捷方式来计算项目符号的数量,从而确定已经识别了多少个主题:
var count = $("li").size();
只有当 html 文档包含至少一个 li 元素时,这个例子才有效。如果没有,您将得到一个错误,说“大小不是一个函数。”你甚至可以循环项目。例如,下面的代码片段将使用 jQuery 的each()
迭代器方法遍历所有的li
元素,在一个警告窗口中显示它们的内容:
$('li').each(function() {
alert(this.html());
});
修改页面元素
jQuery 可以像检索页面元素一样轻松地修改页面元素。例如,要更改书名,只需向检索到的元素的html()
方法传递一个值:
$("#title").html("The Awesomest Book Title Ever");
您不局限于更改元素的内容。例如,让我们创建一个mouseover
事件处理器,它将在用户鼠标经过时向每个列表项添加一个名为highlight
的类:
$("li").mouseover(function(event){
$(this).addClass("highlight");
});
有了这个事件处理器,每当用户将鼠标放在一个列表项上时,这个列表项大概会以某种方式高亮显示,这要感谢一个名为.highlight
的相应 CSS 类所做的一些风格上的改变。当然,一旦用户将鼠标从元素上移开,您可能希望取消高亮显示,因此您还需要创建第二个事件处理器,使用removeClass()
方法将highlight
类从li
元素中分离出来。
作为最后一个例子,假设您希望在用户单击指定元素(如作者姓名)时显示之前隐藏的页面元素。修改 HTML 代码片段,使作者的姓名如下所示:
<span id="author_name">W. Jason Gilmore</span>
ID #author_name
可以像这样在样式表中定义,为用户提供一个线索,虽然名称不一定是超链接,但单击它可能会启动一些任务:
#author_name {
text-decoration: dotted;
}
接下来,在列表项下添加以下代码片段:
<span id="author_bio" style="display: none;">
<h3>About the Author</h3>
<p>
Jason is founder of WJGilmore.com. His interests include solar cooking, ghost chili peppers,
and losing at chess.
</p>
</span>
最后,添加下面的事件处理器,它将在每次用户单击作者的名字时在可见和隐藏状态之间切换#author_bio
DIV:
$("#author_name").click(function(){
$("#author_bio").toggle();
});
到目前为止,您已经了解了 jQuery 如何方便地将事件与元素相关联,以及如何以各种方式解析和操作 DOM。在接下来的两个例子中,您将使用这些概念以及其他一些特性来创建两个 Ajax 驱动的特性,从前面例子提到的用户名存在验证特性开始。
创建用户名存在验证器
在创建新的电子邮件地址或帐户时,特别是在 Yahoo!这样的热门网站上,很少有比反复被告知某个用户名存在更令人沮丧的事情了。似乎每一种可能的组合都被采用了。为了减少挫败感,网站已经开始利用 Ajax 增强的注册表单,它会在提交表单之前自动检查用户名的存在(见图 20-3 ),并通知您结果。在某些情况下,如果用户名被采用,网站会建议一些变化,注册人可能会觉得有吸引力。
图 20-3
雅虎的用户名验证器
让我们创建一个用户名验证器,它非常类似于 Yahoo!在图 20-3 中。为了确定用户名是否已经存在,您需要一个中央帐户存储库作为比较的基础。在现实世界中,这个帐户存储库几乎肯定是一个数据库;但是,因为您还没有深入研究这个主题,所以为了便于说明,我们将使用一个数组。
首先创建注册表(register.php
),如清单 20-1 所示。
<form id="form_register" "action="register.php" method="post">
<p>
Provide Your E-mail Address <br>
<input type="text" name="email" value="">
</p>
<p>
Choose a Username <br />
<input type="text" id="username" name="username" value="">
<a href="nojs.html" class="button" id="check_un">Check Username</a>
</p>
<p>
Choose and Confirm Password<br>
<input type="password" name="password1" value=""> <br>
<input type="password" name="password2" value="">
</p>
<p>
<input type="submit" name="submit" value="Register">
</p>
</form>
Listing 20-1The Registration Form
图 20-4 显示了这个表单在使用时的样子(包括一些小的 CSS 样式化):
图 20-4
行动中的登记表
确定用户名是否存在
接下来,您将创建负责确定用户名是否存在的 PHP 脚本。这是一个非常简单的脚本,任务是连接到数据库并查询accounts
表来确定用户名是否已经存在。然后将根据结果通知用户。脚本(available.php
)在清单 20-2 中给出,后面是一些注释。尽管真实世界的示例会将提供的用户名与存储在数据库中的值进行比较,但是为了避免额外的复杂性,该示例使用了基于数组的存储库。
<?php
// A makeshift accounts repository
$accounts = array("wjgilmore", "mwade", "twittermaniac");
// Define an array which will store the status
$result = array();
// If the username has been set, determine if it exists in the repository
if (isset($_GET['username']))
{
// Filter the username to make sure no funny business is occurring
$username = filter_var($_GET['username'], FILTER_SANITIZE_STRING);
// Does the username exist in the $accounts array?
if (in_array($username, $accounts))
{
$result['status'] = "FALSE";
} else {
$result['status'] = "TRUE";
}
// JSON-encode the array
echo json_encode($result);
}
?>
Listing 20-2Determining Whether a Username Exists
除了最后一条语句,这个脚本的大部分内容现在应该看起来很熟悉了。json_encode()
函数是一个原生的 PHP 函数,可以将任何 PHP 变量转换成 JSON 格式的字符串,随后可以被任何其他支持 JSON 的语言接收和解析。注意,JSON 格式只是一个由一系列键和相关值组成的字符串。例如,如果用户试图使用用户名wjgilmore
注册,返回的 JSON 字符串将如下所示:
{"status":"FALSE"}
当创建 Ajax 增强的特性时,由于移动部件的数量,调试可能是一个艰巨的过程。因此,在进入集成阶段之前,尝试和测试每个部分总是一个好主意。在这个脚本中,因为它希望用户名通过 GET 方法提供,所以您可以通过在命令行传递用户名来测试这个脚本,就像这样: www。举例。com/可用。php?用户名= wjgilmore
集成 Ajax 功能
剩下的唯一一步是集成 Ajax 功能,该功能允许用户在不重新加载页面的情况下确定用户名是否可用。这涉及到使用 jQuery 向available.php
脚本发送一个异步请求,并用适当的响应更新页面的一部分。清单 20-3 中展示了用于实现该特性的特定于 jQuery 的代码。这个代码应该放在包含注册表单的<head>
标签的页面中。
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script type="text/javascript">
$(document).ready(function() {
// Attach a click handler to the Check Username button
$('#check_un').click(function(e) {
// Retrieve the username field value
var username = $('#username').val();
// Use jQuery's $.get function to send a GET request to the available.php // script and provide an appropriate response based on the outcome
$.get(
"available.php",
{username: username},
function(response){
if (response.status == "FALSE") {
$("#valid").html("Not available!");
} else {
$("#valid").html("Available!");
}
},
"json"
);
// Use jQuery's preventDefault() method to prevent the link from being followed
e.preventDefault();
});
});
</script>
Listing 20-3Integrating Ajax into the Username Validation Feature
就像清单 20-2 中展示的 PHP 脚本一样,这里没有什么需要回顾的,因为这些 jQuery 特性在本章前面已经介绍过了。然而,新的是使用了 jQuery 的$.get
函数。该函数接受四个参数,包括应该联系的服务器端脚本的名称(available.php
)、应该传递给脚本的 GET 参数(在本例中是一个名为 username )
的参数)、一个将从 PHP 脚本返回的数据作为输入的匿名函数,以及最后一个声明,该声明指出返回的数据将如何格式化(在本例中是 JSON)。注意 jQuery 如何使用点符号格式轻松解析返回的数据(在本例中,确定如何设置response.status
)。
jQuery 还能够使用其本地的$.post
方法向脚本发送 POST 数据。有关这个有用特性的更多信息,请参考 jQuery 文档。
摘要
对于外行人来说,Ajax 似乎是构建网站的一种非常复杂的方法。然而,正如你在本章中了解到的,这种 web 开发方法只是几种技术和标准协同工作的结果,产生了一个不可否认的很酷的结果。
在下一章中,您将了解到另一个非常有趣的特性,即国际化,尽管它看起来很复杂。通过国际化你的网站,你将能够更有效地迎合不断扩大的来自其他国家的客户和用户。向前!
https://en.wikipedia.org/wiki/Ajax_(programming)
2
https://trends.builtwith.com/javascript/jQuery
二十一、MVC 和框架
即使在你的 web 开发生涯的早期阶段,你可能已经在尝试勾画一个渴望已久的定制网站的功能了。也许是电子商务商店?一个致力于集邮的在线社区论坛?或者更实际的东西,比如公司内部网?不管目的是什么,您都应该努力使用合理的开发实践。近年来,使用这种事实上的最佳实践变得如此重要,以至于几个开发团队联合起来开发了各种各样的 web 框架,其中的每一个都有助于其他人以一种高效、快速且代表合理开发原则的方式开发 web 应用。
本章有三个目的。首先,我将介绍模型-视图-控制器(MVC)设计模式,它为开发人员构建网站提供了一种组织良好的方法。其次,我将介绍几个最流行的 PHP 驱动的框架,每个框架都允许您利用 MVC,以及各种其他节省时间的特性,如数据库和 web 服务集成。最后,我将介绍 PHP 框架互操作性组(PHP-FIG)。这是一个致力于让框架“和谐相处”的团队。
MVC 简介
假设你最近推出了一个新网站,却发现它很快就被用户淹没了。渴望扩展这一新发现的成功,该项目开始在野心和复杂性方面增长。你甚至已经开始雇佣一些有才华的员工来帮助设计和开发。新聘请的设计师立即开始对网站页面进行彻底检查,其中许多页面目前看起来像这样:
<?php
// Include site configuration details and page header
INCLUDE "config.inc.php";
INCLUDE "header.inc.php";
// Scrub some data
$eid = htmlentities($_POST['eid']);
// Retrieve desired employee's contact information
$query = "SELECT last_name, email, tel
FROM employees
WHERE employee_id='$eid'";
$result = $mysqli->query($query, MYSQLI_STORE_RESULT);
// Convert result row into variables
list($name, $email, $telephone) = $result->fetch_row();
?>
<div id="header">Contact Information for: <?php echo $name; ?>
Employee Name: <?php echo $name; ?><br />
Email: <?php echo $email; ?><br />
Telephone: <?php echo $telephone; ?><br />
<div id="sectionheader">Recent Absences
<?php
// Retrieve employee absences in order according to descending date
$query = "SELECT absence_date, reason
FROM absences WHERE employee_id='$eid'
ORDER BY absence_date DESC";
// Parse and execute the query
$result = $mysqli->query($query, MYSQLI_STORE_RESULT);
// Output retrieved absence information
while (list($date, $reason) = $result->fetch_row();
echo "$date: $reason";
}
// Include page footer
INCLUDE "footer.inc.php";
?>
因为设计和逻辑不可避免地交织在一起,很快就出现了几个问题:
-
由于网站的设计和逻辑的混合,那些被雇佣的唯一目的是让你的网站看起来很棒的设计师现在面临着不得不学习 PHP 的任务。
-
被雇来帮助扩展网站功能的开发人员,正忙着修复由设计师的新手 PHP 代码引入的错误和安全问题。在这个过程中,他们决定对网站设计做一些小小的调整,这激怒了设计师。
-
由于同时编辑同一组文件而引起的几乎不断的冲突很快变得令人厌烦和耗时。
您可能注意到了这里的一个模式:缺乏关注点的分离正在滋生一个痛苦、不信任和低效的环境。但是有一个解决方案可以大大缓解这些问题:MVC 架构。
MVC 方法通过将应用分成三个不同的组件来提高开发效率:模型*、视图和控制器。这样做允许独立地创建和维护每个组件,从而最小化组件以类似于前一示例中所示的方式缠绕时产生的残余影响。您可以在其他学习资源中找到每个组件的详细定义,但对于本简介而言,以下内容就足够了:*
-
模型:模型为你的网站建模的领域指定了规则,定义了应用的数据和行为。例如,假设您创建了一个用作转换计算器的应用,允许用户将磅转换为千克,将英尺转换为英里,将华氏温度转换为摄氏温度,以及其他单位。模型负责定义用于执行此类转换的公式,当提供值和所需的转换场景时,模型执行转换并返回结果。请注意,模型不负责格式化数据或将数据呈现给用户。这是由视图处理的。
-
视图:视图负责将模型返回的数据格式化并呈现给用户。一个以上的视图可以利用同一个模型,这取决于数据应该如何呈现。例如,您可以为转换应用提供两个接口:一个针对标准浏览器,另一个针对移动设备进行了优化。
-
控制器:控制器负责确定应用应该如何基于应用空间内发生的事件(通常是用户动作)做出响应,通过协调模型和视图来产生适当的响应。一个被称为前端控制器的特殊控制器负责将所有请求路由到适当的控制器并返回响应。
为了帮助您更好地理解 MVC 驱动的框架的动态性,下面的示例通过一个涉及转换器应用的典型场景,突出了每个 MVC 组件的角色:
-
用户与视图交互以指定他想要执行的转换类型,例如,将输入温度从华氏温度转换为摄氏温度。
-
控制器通过识别适当的转换动作、收集输入并将其提供给模型来做出响应。
-
该模型将该值从华氏温度转换为摄氏温度,并将结果返回给控制器。
-
控制器调用适当的视图,传递计算出的值。视图呈现结果并返回给用户。
PHP 的框架解决方案
虽然 PHP 一直非常适合使用 MVC 方法进行开发,但直到 Ruby on Rails ( https://www.rubyonrails.org
)的突然成功吸引了全球 web 开发人员的注意,才出现了一些可用的解决方案。PHP 社区对这种新出现的对框架的需求做出了回应,并大量借鉴了 Rails 和许多其他 MVC 框架所支持的引人注目的特性。本节重点介绍了五个比较突出的 PHP 专用解决方案。这些框架可以自动化 CRUD(创建、检索、更新、删除)数据库操作,执行数据缓存,过滤表单输入;它们支持一长串选项和插件,使发送电子邮件、创建 PDF 文档、与 web 服务集成以及执行 web 应用中常用的其他任务变得容易。
注意
您还会发现,本节介绍的每个框架都比 MVC 实现提供了更多的功能。例如,它们都有助于 Ajax 集成、表单验证和数据库交互。我们鼓励您仔细研究每个框架的独特特性,以便确定哪个最适合您的特定应用的需求。
CakePHP 框架
在本节描述的四个解决方案中,CakePHP ( https://www.cakephp.org
)最接近 Rails,实际上它的开发人员很乐意提到这个项目最初是受 breakout 框架的启发。该项目由 Michal Tatarynowicz 于 2005 年创建,此后吸引了数百名活跃分子的兴趣。
CakePHP 框架可以使用 Composer 安装,命令如下:
$ composer require cakephp/cakephp
Symfony 框架
symfony 框架(【https://symfony.com/】)是法国网络开发公司 Sensio ( www.sensio.com
)的创始人杨奇煜·庞蒂尔的创意。Symfony 建立在其他几个成熟的开源解决方案之上,包括对象关系映射工具 Doctrine 和 Propel。通过消除在创建这些组件时产生的额外开发时间,Symfony 的开发人员能够专注于创建大大加快应用开发时间的功能。Symfony 的用户还可以利用自动表单验证、分页、购物车管理和使用 jQuery 等库的直观 Ajax 交互。
Symfony 框架可以使用以下命令与 Composer 一起安装:
$ composer create-project symfony/website-skeleton my-project
Zend 框架
Zend Framework(zendframework.com/
)是一个开源项目,由著名的 PHP 产品和服务提供商 Zend Technologies(www.zend.com
)开发。它提供了各种特定于任务的组件,能够执行当今尖端 web 应用的重要任务。
Zend 框架可以使用 Composer 安装,命令如下:
$ composer require zendframework/zendframework
如果您只是对 Zend Framework 的 MVC 部分感兴趣,您可以使用这个命令:
$ composer require zendframework/zend-mvc
费尔康框架
Phalcon 框架的核心(phalconphp.com/en/
)是作为 PHP 扩展用 C 语言编写的。这提供了路由和框架其他部分的快速执行,但也使得扩展更加困难。可以从编译该扩展的源代码进行安装,也可以使用 Debian/Ubuntu 或 CentOS 上的软件包管理器,使用以下命令进行安装:
$ sudo apt-get install php7.0-phalcon
或者
$ sudo yum install php70u-phalcon
在 Windows 上,您必须下载 php_phalcon.dll 文件,并将下面一行添加到 php.ini 文件中:
extension=php_phalcon.dll
记住在对 php.ini 进行修改后重启 web 服务器。
Laravel 框架
Laravel Framework(laravel.com/
)是一个全栈的 web 应用框架,专注于表达性和优雅的语法,并试图通过使大多数 web 应用中执行的常见任务变得容易来消除开发的痛苦。这些任务包括认证、路由、会话处理和缓存。该框架易于学习,并且有很好的文档记录。
可以使用 Composer 和以下命令安装 Laravel:
$ composer global require "laravel/installer"
这将创建一个 Laravel 安装包的全局安装,可以用来创建多个站点。一个二进制文件将被安装在 Mac 上的$HOME/.composer/vendor/bin
和 Linux 发行版上的$HOME/.config/composer/vendor/bin
中。为了创建一个新的 Laravel 应用,可以使用 Laravel 命令:
$ ~/.config/composer/vendor/bin/Laravel new blog
这将在当前工作目录中创建一个名为 blog 的目录,并安装配置站点所需的所有部件。缺少的只是 web 服务器的配置。将文档根目录设置为 blog/public 文件夹,并重新启动 web 服务器。您还必须将所有文件的所有权设置给运行 web 服务器的用户。这将允许 Laravel 在目录结构中写入日志文件和其他信息。
将网络浏览器指向新创建的网站将会提供一个看起来如图 21-1 所示的页面。
图 21-1
新 Laravel 网站的默认内容
安装好框架后,是时候编写第一个应用了。Laravel 框架使用模型视图控制器(MVC)模式将设计/布局从数据库模型和业务逻辑中分离出来,它提供了一个允许创建简单 URL 的路由系统。路由将一个 URL 链接到一个特定的 PHP 文件(控制器),在某些情况下直接链接到一个布局(视图)。路由保存在名为 routes/web.php 的文件中。在同一目录中还有用于其他目的的路由文件,但是 web.php 文件用于与 web 应用相关的路由。在下面的例子中,我们将创建一个简单的应用来转换不同的长度单位。该应用不需要模型,因为不涉及数据库。它是通过一个视图实现的,该视图定义了一个输入表单的布局,该表单用于输入要转换的单位和选择要转换的单位。应用的第二部分是有两个动作的控制器。第一个动作是表单动作,用于显示表单。第二个动作是计算动作,它将接受输入值并计算结果。结果将作为 JSON 对象返回,JavaScript 代码随后使用该对象更新输出值。应用将有两条路线:第一条显示表单,第二条执行计算。这些路由在 routes 文件中定义,如下所示:
<?php
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/convert', 'ConvertController@form');
Route::post('/calculate', 'ConvertController@calc');
这两个路由被定义为 get 和 post 方法。他们使用相同的控制器,但两个不同的行动。控制者将住在app/Http/Controllers
并被称为 ConvertControler.php。创建路由时不包括 php 扩展名,而是包括用于控制器的类名,如清单 21-1 所示。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ConvertController extends Controller
{
/**
* Show the conversion form
*
* @return \Illuminate\Http\Response
*/
public function form()
{
return view('convertForm');
}
/**
* Show the conversion form
*
* @return \Illuminate\Http\Response
*/
public function calc()
{
return response()->json([
'to' => round($_POST['from'] * $_POST['fromUnit'] / $_POST['toUnit'], 2),
]);
}
}
Listing 21-1ConvertController.php
form 方法使用 view 函数来生成输出。视图文件存储在resources/view
中,在这种情况下,该文件称为convertForm.blade.php
。使用这种命名约定是因为 Laravel 使用的是刀片模板系统。这个例子的视图如清单 21-2 所示。
<!doctype html>
<html lang="{{ app()->getLocale() }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Unit Converter</title>
<!-- Fonts -->
<link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet" type="text/css">
<!-- Styles -->
<style>
html, body {
background-color: #fff;
color: #636b6f;
font-family: 'Nunito', sans-serif;
font-weight: 200;
height: 100vh;
margin: 0;
}
.full-height {
height: 100vh;
}
.flex-center {
align-items: center;
display: flex;
justify-content: center;
}
.position-ref {
position: relative;
}
.top-right {
position: absolute;
right: 10px;
top: 18px;
}
.content {
text-align: center;
}
.title {
font-size: 32px;
}
.links > a {
color: #636b6f;
padding: 0 25px;
font-size: 12px;
font-weight: 600;
letter-spacing: .1rem;
text-decoration: none;
text-transform: uppercase;
}
.m-b-md {
margin-bottom: 30px;
}
</style>
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
</head>
<body>
<div class="flex-center position-ref full-height">
<div class="content">
<div class="title m-b-md">
Unit Converter
</div>
<div class="links">
<form id="convertForm" method="POST" action="/calculate">
@csrf
<input id="from" name="from" placeholder="From" type="number">
<select id="fromUnit" name="fromUnit">
<option value="25.4">Inch</option>
<option value="304.8">Foot</option>
<option value="1">Millimeter (mm)</option>
<option value="10">Centimeter (cm)</option>
<option value="1000">Meeter (m)</option>
</select>
<br/>
<input id="to" placeholder="To" type="number" disabled>
<select id="toUnit" name="toUnit">
<option value="25.4">Inch</option>
<option value="304.8">Foot</option>
<option value="1">Millimeter (mm)</option>
<option value="10">Centimeter (cm)</option>
<option value="1000">Meeter (m)</option>
</select>
<br/>
<button type="submit">Calculate</button>
</form>
</div>
</div>
</div>
<script>
$("#convertForm").submit(function( event ) {
// Stop form from submitting normally
event.preventDefault();
// Get some values from elements on the page:
var $form = $( this ),
t = $form.find("input[name='_token']").val(),
f = $form.find("#from").val(),
fU = $form.find("#fromUnit").val(),
tU = $form.find("#toUnit").val(),
url = $form.attr("action");
// Send the data using post
var posting = $.post( url, { _token: t, from: f, fromUnit: fU, toUnit: tU } );
// Put the results in a div
posting.done(function( data ) {
$("#to").val(data.to);
});
});
</script>
</body>
</html>
Listing 21-2convertForm.blade.php
在这种情况下,没有真正的 PHP 代码嵌入到模板中,但是可以引用模型中的变量和数据(如果使用的话)以及模板中的其他结构。向服务器发送数据和检索内容是由 AJAX 请求来处理的,通过使用 jQuery 库,这变得很容易。
在请求时用/convert 将网络浏览器指向服务器地址,将显示如图 21-2 所示的单位转换器表单。
图 21-2
单位转换表
在“从”字段中输入 25,选择英寸和毫米作为换算的单位,并点击计算按钮,结果应如图 21-3 所示。
图 21-3
结果
PHP 框架互用性组织(PHP-FIG)
PHP 框架互操作性小组是一个协作性的工作组,通常由来自许多框架和项目的代表组成。该组织的目标是推进 PHP 生态系统和促进良好的标准。该组织推广 PHP 标准建议(PSRs),这是一组被项目和开发人员接受和使用的标准。这些标准包括从基本编码指南(PSR-1 和 PSR-2)到自动加载(PSR-4)和缓存(PSR-6)。当项目遵循这些标准时,就有可能将一个项目的一部分包含在另一个项目中,或者其他开发人员有可能在不破坏整个项目的情况下对项目进行添加或替换。
PSR-1 和 PSR-2 编码标准
PSR-1 和 PSR-2 中描述了基本的编码标准。https://www.php-fig.org/psr/psr-1/-1()并通过陈述以下规则来定义 PHP 文件应该如何组织:
-
文件必须仅使用
-
文件必须只使用 PHP 代码没有 BOM 的 UTF-8。
-
文件应该或者声明符号(类、函数、常量等。)或引起副作用(如,产生输出,改变。ini 设置等。)但不应该两者兼而有之。
-
命名空间和类必须遵循“自动加载”PSR: [PSR-4]。
-
类名必须在 StudlyCaps 中声明。
-
类常量必须全部用大写字母声明,并带有下划线分隔符。
-
方法名必须在 camelCase 中声明。
PSR-2 标准包含更多的要求,并且是关于代码布局和可读性的。PSR-2 扩展了 PSR-1 标准,旨在使遵循同一组编码标准的多个项目更容易协作:
-
代码必须遵循“编码风格指南”PSR-1。
-
代码必须使用 4 个空格缩进,而不是制表符。
-
对线路长度不能有硬性限制;软限制必须为 120 个字符;行数应等于或少于 80 个字符。
-
在命名空间声明后必须有一个空行,在 use 声明块后必须有一个空行。
-
类的左括号必须在下一行,右括号必须在主体后的下一行。
-
方法的左大括号必须在下一行,右大括号必须在正文后的下一行。
-
必须在所有属性和方法上声明可见性;必须在可见性之前声明 abstract 和 final 必须在可见性之后声明 static。
-
控制结构关键字后面必须有一个空格;方法和函数调用不能。
-
控制结构的左大括号必须在同一行,右大括号必须在主体后的下一行。
-
控制结构的左括号后面不能有空格,控制结构的右括号前面不能有空格。
即使您没有使用或参与一个框架,遵循这些建议也是一个很好的实践,特别是如果多个开发人员在同一个项目上合作,或者您在某个时候想要增加项目开发人员的数量,或者甚至可能将您的项目作为开源项目。
PSR-4 自动装弹
当类第一次在脚本中使用时,自动加载是一个允许 PHP 包含或要求包含类定义的文件的特性。正确配置自动加载后,您将不必编写一长串 include 或 require 语句来确保代码运行无误。使用名称空间对于避免同名的多个类之间的冲突变得非常重要。这是 Composer 依赖管理系统的基石之一。PSR-4 标准定义了这些规则:
-
术语“类”指的是类、接口、特征和其他类似的结构。
-
完全限定的类名具有以下形式:
-
\ (\ )*<classname>
-
完全限定类名必须有一个顶级名称空间名称,也称为“供应商名称空间”
-
完全限定类名可以有一个或多个子命名空间名。
-
完全限定类名必须有一个终止类名。
-
下划线在完全限定类名的任何部分都没有特殊含义。
-
完全限定类名中的字母字符可以是小写和大写的任意组合。
-
所有类名必须以区分大小写的方式引用。
-
-
当加载对应于完全限定类名的文件时…
-
完全限定类名中的一个或多个前导命名空间和子命名空间名称的连续系列(不包括前导命名空间分隔符)(“命名空间前缀”)对应于至少一个“基目录”
-
“名字空间前缀”之后的连续子名字空间名称对应于“基本目录”中的子目录,其中名字空间分隔符表示目录分隔符。子目录名称必须与子名称空间名称的大小写匹配。
-
终止类名对应于以. php 结尾的文件名。文件名必须与终止类名的大小写匹配。
-
-
自动加载器实现不得抛出异常,不得引发任何级别的错误,并且不得返回值。
Composer 附带的自动加载器功能是通过包含文件vendor/autoload.php
来实例化的。您可以将供应商目录添加到 php.ini 文件的include_path
中,并在脚本中简单地使用require "autoload.php";
,假设在包含路径中只有一个 autoload.php 文件。
摘要
框架可以帮助开发人员专注于业务逻辑,而不是如何进行身份验证、如何创建访问控制或者如何为特定的布局格式化输出。PHP 社区已经构建了许多框架来简化这些任务。构建什么样的 web 应用并不重要;您将能够找到一个框架,可以为您解决大多数繁琐的任务。你需要做的就是找到一个有你需要的功能的,这样你就可以专注于实际的网站功能和外观。
今天可用的大多数框架还包括某种形式的数据库连接服务,允许您从流行的数据库中选择作为 web 应用的后端。PHP 使用的最流行的数据库之一是 MySQL 数据库,将在后面的章节中介绍。
二十二、MySQL 简介
MySQL 关系数据库服务器诞生于大约 22 年前的一个公司内部项目,由一家瑞典软件公司的员工开发。他们的项目被称为 MySQL,于 1996 年底首次向公众发布。事实证明,这款软件非常受欢迎,以至于他们在 2001 年成立了一家新公司,专门提供 MySQL 服务和产品。在接下来的十年中,MySQL 在教育机构、政府机构、小型企业和财富 500 强公司中的采用率如此之高,以至于 MySQL 背后的公司在 2008 年被 Sun Microsystems 以近 10 亿美元的价格收购,该公司又在 2009 年初被 Oracle Corporation 收购。这是一个相当惊人的成功故事!那么,到底是什么让 MySQL 的产品如此吸引人呢?
一个特别的原因是开发团队的历史思维。从第一次公开发布开始,MySQL 的开发人员就特别强调速度和可伸缩性,这两个特性对全球的开发人员来说非常有吸引力,因为他们对构建高性能的网站很感兴趣。然而,这些优势是有代价的,因为 MySQL 是一个高度优化的产品,缺少许多被认为是企业数据库产品标准的特性:例如,存储过程、触发器和事务。然而,该产品吸引了大量用户的注意,他们对速度和可伸缩性更感兴趣,而不是在许多情况下经常被闲置的功能。后来的版本最终增加了这些功能,吸引了更多的用户。
根据 MySQL 网站,该产品已被下载超过 1 亿次。该数据库的用户包括世界上许多行业中一些最知名的公司和组织,包括 YouTube、PayPal、网飞和脸书( www.mysql.com/customers
)。在这一章的后面,我将仔细看看这些用户是如何使用 MySQL 的,在某些情况下,在这个过程中节省了数百万美元。
是什么让 MySQL 如此受欢迎?
MySQL 是一个关系数据库服务器,它提供了与竞争产品相同的特性。换句话说,如果您熟悉另一个数据库产品,就不会遇到太多意外。除了众所周知的方便定价选项(具体来说,它在很多情况下都是免费的),MySQL 的什么地方让它如此受欢迎呢?这一节重点介绍了一些有助于其人气飙升的关键特征。
灵活性
无论你运行的是什么操作系统,MySQL 都有可能覆盖你。在 MySQL 网站上,您会找到适用于 14 个平台的优化二进制文件:康柏 Tru64、DEC OSF、FreeBSD、IBM AIX、惠普 UX、Linux、Mac OS X、Novell NetWare、OpenBSD、QNX、SCO、SGI IRIX、Solaris(版本 8、9 和 10)和 Microsoft Windows。软件包也适用于 Red Hat、SUSE 和 Ubuntu。此外,如果您的平台没有二进制文件,或者如果您想自己执行编译,MySQL 会提供源代码供下载。
各种 API 也适用于所有最流行的编程语言,包括 C、C++、Java、Perl、PHP、Ruby 和 Tcl。
MySQL 还提供了许多类型的数据管理机制,称为存储引擎。谨慎选择特定存储引擎的重要性类似于为特定任务使用适当算法的重要性。像算法一样,存储引擎特别擅长某些任务,但可能不适用于其他任务。MySQL 很早就支持几个引擎,其中几个在第二十六章中有介绍。
尽管 MySQL 默认使用英语兼容的设置,但它的开发者认识到并非所有用户都来自英语国家,因此 MySQL 允许用户从超过 35 个字符集中进行选择。您可以使用这些字符集来控制错误和状态消息使用的语言、MySQL 如何对数据进行排序以及数据如何存储在表中。
力量
从最早的版本开始,MySQL 开发人员就一直关注性能,即使是以减少特性集为代价。直到今天,对非凡速度的承诺没有改变,尽管随着时间的推移,以前缺乏的能力已经发展到可以与许多商业和开源竞争对手相媲美。本节简要介绍了该产品的一些更有趣的性能和特性相关的特征。
企业级 SQL 特性
正如本章介绍中提到的,MySQL 有一段时间缺乏高级特性,比如子查询、视图和存储过程。但是,这些特性(以及更多特性)是在以后的版本中添加的,这使得数据库在企业环境中的应用越来越多。本书随后的几章将专门介绍这些相对较新的特性。
全文索引和搜索
MySQL 长期以来一直支持全文索引和搜索,这些功能极大地增强了从基于文本的列中挖掘数据的性能。此功能还使您能够根据查询与行的索引文本列的匹配程度,按相关性顺序生成结果。该功能在第三十三章中介绍。
查询缓存
查询缓存是 MySQL 最大的速度提升之一。启用时,查询缓存简单而高效,允许 MySQL 在内存中存储选择查询及其相应的结果。随着后续查询的执行,MySQL 会将它们与缓存的查询进行比较;如果匹配,MySQL 将放弃代价高昂的数据库检索,转储缓存的查询结果。为了消除过时的结果,机制可用于自动移除无效的缓存结果,并在下一次请求时重新缓存它们。
分身术
复制允许位于一个 MySQL 服务器上的数据库被复制到另一个服务器上,这提供了很多优点。例如,只要有一个复制的数据库就可以大大提高可用性,因为如果主数据库出现问题,它可以立即联机。如果有多台机器供您使用,客户机查询可以分布在主服务器和多个从服务器上,从而大大减少了单台机器上的负载。另一个优势涉及备份;在备份完成时,您不必让应用离线,而是可以在从属服务器上执行备份,从而避免任何停机时间。
配置和安全性
MySQL 拥有大量的安全和配置选项,使您能够完全控制其操作的几乎每一个可以想象的方面。例如,使用 MySQL 的配置选项,您可以控制如下功能:
-
守护进程所有者、默认语言、默认端口、MySQL 数据存储的位置以及其他关键特征。
-
分配给各种 MySQL 资源(如查询缓存)的内存量。
-
MySQL 网络功能的各个方面,包括在中止之前它将尝试执行连接多长时间,它是否将尝试解析 DNS 名称,允许的最大数据包大小等等。
此外,MySQL 跟踪关于数据库交互的所有方面的许多指标,例如总的传入和传出字节数;执行的每个查询类型的计数;以及打开、运行、缓存和连接的线程总数。它还跟踪超过特定执行阈值的查询数量、存储在缓存中的查询总数、正常运行时间等等。这些数字对于在服务器的整个生命周期中不断调整和优化服务器来说是无价的。
MySQL 的安全选项同样令人印象深刻,允许您管理如下特征:
-
对于给定的数据库、表甚至列,用户可以执行哪些操作。例如,您可能允许用户对公司雇员表的电子邮件列拥有更新权限,但拒绝删除权限。
-
每小时允许的查询、更新和连接的总数。
-
用户是否必须提供有效的 SSL 证书才能连接到数据库。
由于这些选项的重要性,在接下来的章节中会反复提到它们。具体来说,第二十三章的一部分致力于 MySQL 的配置,第二十六章的全部致力于 MySQL 的安全性。
灵活的许可选项
MySQL 提供了两个许可选项,这两个选项都将在本节中介绍。
MySQL 开源许可证
Oracle 根据 GNU 通用公共许可证(GPL)条款提供其软件的免费社区版本。如果您在自己的服务器上下载和使用该软件,与付费许可证相比,这不会给您任何超出社区版本限制的限制。如果您决定开发和销售包含 MySQL 的 GPL 许可部分的软件,您将被要求在相同的许可下发布您的软件或支付商业许可。在 https://www.fsf.org/licensing/licenses/gpl.html
了解更多关于 GPL 条款的信息。
认识到并非所有用户都希望在 GPL 的限制性条款下发布他们的软件,MySQL 也可以作为 Oracle cloud 中的云服务使用,他们提供企业版。
标准、企业和云许可证
MySQL 目前提供三种商业许可,分别叫做标准、企业和云。这些将提供附加功能和支持以及产品升级的组合。完整的特性列表可以在这里找到: https://www.mysql.com/products/
。
您应该使用哪种许可证?
当你读这本书的时候,你很可能是一个正在为你自己或者你工作的公司构建应用的开发者。在大多数情况下,开源许可证将是你所需要的。另一方面,如果您需要访问一些更高级的功能,如热备份或加密和压缩,或者您计划开发一个嵌入 MySQL 的产品,您将不得不考虑一个商业许可证,或者在与您必须投资的 MySQL 开源版本相同的 GPL 许可证下许可整个软件
杰出的 MySQL 用户
如前所述,MySQL 拥有相当多的杰出用户。我选择了两个更引人注目的实现来提供 MySQL 如何帮助您的组织的额外见解。
克雷格列表(网站名)
自 1995 年成立以来,广受欢迎的在线分类广告和社区网站 craigslist ( https://www.craigslist.org
)一直在不断扩张。craigslist 网站从一开始就依赖于各种开源产品,包括 LAMP (Linux、Apache、MySQL、Perl)栈(参见 https://www.craigslist.org/about/thanks
了解该公司使用的著名开源产品列表)。这个软件为一个负责发布超过 1 亿条分类广告和每月惊人的 500 亿次页面浏览的社区提供了动力( https://www.craigslist.org/about/factsheet
)!
推特
短短几年间,Twitter 已经发展到像可口可乐和麦当劳一样无处不在,事实上,其数亿用户中的许多人认为这项服务像食物和水一样不可或缺。MySQL 在消息服务每秒存储数万条推文的能力中发挥了关键作用,每天的消息总数达到惊人的 5 亿条。 1 性能是如此重要,该公司甚至还维护了自己的 MySQL 开发分支,可通过 GitHub: https://github.com/twitter/mysql
获得。
当然,这种规模的基础设施依赖于多种技术,MySQL 只是用于支持该服务的几种存储解决方案之一。其他存储技术包括 Cassandra ( https://cassandra.apache.org/
)和 Hadoop ( https://hadoop.apache.org
/)。
开源代码库
GitHub 使用 MySQL 和 Rails 应用的组合为其用户提供基础设施和服务。GitHub 还开发了开源应用,可以帮助用户进行模式迁移(gh-ost)。你可以在这里阅读更多关于 GitHub 的故事: https://www.mysql.com/customers/view/?id=1265
。
其他主要用户
MySQL 网站提供了一系列以知名 MySQL 用户( https://mysql.com/why-mysql/case-studies/
)为特色的案例研究,其中包括威瑞森无线、沃尔玛、Anritsu 和 Zappos。考虑花些时间仔细阅读这些摘要,因为它们可以作为游说您的组织在企业中采用 MySQL 的有用武器。
Maria db:MySQL 的替代品
在 MySQL 被 Sun Microsystems 以及后来的 Oracle(数据库市场的竞争对手)收购后,一些核心开发人员觉得他们已经减少了对产品方向和功能的影响,因此他们创建了产品的“分支”。它被命名为 MariaDB。它被迅速采用,主要是因为它与原始产品非常兼容,但也因为它在某些情况下提供了更好的性能。随着这两种产品的发展,它们之间的差距可能会越来越大,从一种产品移植到另一种产品可能会变得更加困难。
许多 Linux 发行版现在默认提供 MariaDB 版本,用户必须使用特殊的下载选项来安装原生 MySQL 版本。
MySQL 安装的另一个替代项目是 Percona 服务器项目( https://www.percona.com/software/mysql-database/percona-server
),它也是由前 MySQL 开发人员重新创建的。
摘要
从内部项目到全球竞争对手,MySQL 自诞生以来确实走过了漫长的道路。这一章简要概述了 MySQL 的发展历程,详细介绍了 MySQL 的历史、进步和未来。还展示了数千个成功用户案例中的几个,突出了 MySQL 在具有全球影响力的组织中的使用。
在接下来的章节中,您将进一步熟悉许多 MySQL 基础主题,包括安装和配置过程、许多 MySQL 客户端、表结构和 MySQL 的安全特性。如果您是 MySQL 的新手,那么这些资料对于快速了解这个强大的数据库服务器的基本特性和行为是非常宝贵的。如果你已经非常熟悉 MySQL,尽管如此,还是要考虑浏览这些资料;至少,它应该是一个有用的参考。
https://en.wikipedia.org/wiki/Twitter
二十三、安装和配置 MySQL
本章将指导您完成 MySQL 的安装和配置过程。它并不打算取代 MySQL 优秀的(庞大的)用户手册,而是强调那些希望快速有效地准备好数据库服务器以供使用的人直接感兴趣的关键过程。涵盖了以下主题:
-
下载说明
-
分布变化
-
安装程序(源代码、二进制代码、rpm)
-
设置 MySQL 管理员密码
-
启动和停止 MySQL
-
将 MySQL 安装为系统服务
-
MySQL 配置和优化问题
-
重新配置 PHP 以使用 MySQL
通过本章的总结,您将学会如何安装和配置一个可运行的 MySQL 服务器。
下载 MySQL
MySQL 数据库有两个版本:MySQL 社区服务器和 MySQL 企业服务器。如果您不需要 MySQL 的一系列支持、监控和优先更新服务,您应该使用前者。如果上述任何或所有服务可能吸引你,请通过 https://www.mysql.com/products/enterprise
了解更多关于 MySQL Enterprise 的信息。这本书假设你使用的是社区服务器版,可以通过 MySQL 网站免费下载。
要下载最新的 MySQL 版本,请导航至 https://www.mysql.com/downloads
。从那里,你可以选择 10 种不同的操作系统,或者你可以下载源代码。
如果您运行的是 Linux 或 OS X,我强烈建议您使用发行版的软件包管理器来安装 MySQL。否则,您可以使用可用的 rpm 或来自 https://www.MySQL.com
的源代码来安装 MySQL。在本章的后面,我将指导你从 RPM 和源代码安装 MySQL。
MySQL 提供了范围广泛的软件包供下载,从服务器软件包到集群版本,以及在 Windows 上用于开发或生产环境的捆绑工具。如果你去 https://dev.mysql.com/downloads
你可以看到一个完整的可用软件包列表。类似的你可以去 https://mariadb.com/downloads/
下载当前版本的 MariaDB。
安装 MySQL
数据库服务器安装通常是一个痛苦的过程。幸运的是,MySQL 服务器的安装相当简单。事实上,经过几次迭代后,您会发现未来的安装或升级会话只需几分钟即可完成,甚至可以通过内存来完成。
在本节中,您将学习如何在 Linux 和 Windows 平台上安装 MySQL。除了提供全面的分步安装说明之外,还讨论了经常让新手和普通用户感到困惑的主题,包括发行版格式的变化、特定于系统的问题等等。
注意
在本章的剩余部分,常量INSTALL-DIR
被用作 MySQL 的基本安装目录的占位符。考虑修改您的系统路径以包含此目录。
在 Linux 上安装 MySQL
尽管 MySQL 已经移植到至少 10 个平台上,但它的 Linux 发行版仍然是最受欢迎的。这并不奇怪,因为 Linux 通常与运行基于 web 的服务结合使用。本节涵盖了 MySQL 所有三种可用 Linux 发行版格式的安装过程:RPM、二进制和源代码。此外,它可以通过大多数 Linux 发行版包管理器(yum、apt-get 等)获得。这通常是安装和管理 MySQL 的最简单和最好的方法。不需要处理编译器或手动安装。
RPM、二进制或源代码?
面向 Linux 操作系统的软件通常提供几种分发格式。MySQL 也不例外,为每个发布的版本提供 RPM、二进制和源代码版本。因为这些都是受欢迎的选项,所以本节提供了所有三个选项的说明。如果您不熟悉这些格式,那么在选定一种格式之前,请仔细阅读每一部分,并在必要时进行额外的研究。
RPM 安装过程
如果您运行的是 RPM 驱动的 Linux 发行版,RPM 包管理器(RPM)提供了安装和维护软件的简单方法。RPM 为安装、升级、卸载和查询软件提供了一个通用的命令接口,极大地消除了一般 Linux 软件维护所需要的学习曲线。
小费
尽管您将在本节中学习一些 RPM 更有用和更常用的命令,但它几乎没有触及其功能的皮毛。如果你对 RPM 格式不熟悉,可以在 www.rpm.org
了解更多。
MySQL 为各种不同的处理器架构提供 rpm。要实现本书剩余部分中的示例,您只需要下载 MySQL-server 和 MySQL-client 包。下载这些包,将它们保存到您首选的分发存储库目录中。通常将包存储在/usr/src
目录中,但是位置对安装过程的最终结果没有影响。
您可以用一个命令安装 MySQL 服务器 RPM。例如,要安装在撰写本文时可用的面向 32 位 x86 平台的服务器 RPM,请执行以下命令:
%>rpm -i mysql-community-server-5.7.19-1.el7.x86_64.rpm
您可以考虑添加–v
选项,以便在 RPM 安装时查看进度信息。执行后,安装过程将开始。假设一切顺利,您将被告知初始表已经安装,mysqld 服务器守护进程已经启动。
请记住,这只会安装 MySQL 的服务器组件。如果您想从同一台机器连接到服务器,您需要安装客户机 RPM:
%>rpm -iv mysql-community-client-5.7.19-1.el7.x86_64.rpm
大多数 Linux 安装都提供了一个包管理工具,可以自动识别最新版本。在 Red Hat/CentOS 上,这个工具叫做 yum。为了从 CentOS 7 上的存储库中安装 MariaDB,您将使用以下命令:
%>yum install mariadb mariadb-server
这将安装 MariaDB 的客户端和服务器元素。仍然可以在 CentOS 上安装 MySQL 版本,但它不再是首选/受支持的版本。
同样,如果您使用 Debian 或 Ubunto,您将使用 apt-get 命令来安装软件包:
%>apt-get install mysql-server
这个命令将实际安装服务器的 MariaDB 版本。
信不信由你,通过执行这个简单的安装命令,初始数据库已经创建好了,MySQL 服务器守护进程正在运行。
小费
卸载 MySQL 就像安装它一样简单,只需要一个命令:
%>rpm –e MySQL-VERSION
尽管 MySQL RPMs 提供了一种无痛且有效的手段,但这种便利是以灵活性为代价的。例如,安装目录是不可重定位的;也就是说,您被绑定到由打包程序确定的预定义安装路径。这不一定是一件坏事,但是灵活性通常很好,有时也是必要的。如果您的个人情况需要这种额外的灵活性,请继续阅读以了解二进制和源代码安装过程。否则,继续“设置 MySQL 管理员密码”一节。
二进制安装过程
二进制发行版是简单的预编译源代码,通常由开发人员或贡献者创建,旨在为用户提供特定于平台的优化发行版。虽然本章主要关注 Linux 的安装过程,但是请记住,除了 Windows 之外,所有平台的安装过程基本相同(许多平台可以在 MySQL 网站上下载), Windows 将在下一节介绍。
要在 Linux 上安装 MySQL 二进制文件,您需要有能够解压缩二进制文件包的工具。大多数 Linux 发行版都带有 GNU gunzip 和tar
工具,它们能够执行这些任务。
您可以通过导航到 MySQL 网站的下载部分来下载适用于您的平台的 MySQL 二进制文件。与 rpm 不同,二进制文件将服务器和客户机打包在一起,所以您只需要下载一个包。下载这个包,并将其保存到您首选的分发存储库目录中。将包存储在/usr/src
目录中是很常见的,但是这个位置对安装过程的最终结果没有影响。
尽管就击键而言,二进制安装过程比安装 RPM 稍微复杂一些,但就所需的 Linux 知识而言,它只是稍微复杂一些。这个过程可以分为四个步骤:
-
创建必要的组和所有者(对于此步骤和以下步骤,您需要具有 root 权限):
%>groupadd mysql %>useradd –g mysql mysql
-
将软件解压缩到目标目录。建议使用 GNU
gunzip
和tar
程序。%>cd /usr/local %>tar -xzvf /usr/src/mysql-VERSION-OS.tar.gz
-
将安装目录链接到一个共同点:
%>ln -s FULL-PATH-TO-MYSQL-VERSION-OS mysql
-
安装 MySQL 数据库。
mysql_install_db
是一个 shell 脚本,它登录到 MySQL 数据库服务器,创建所有必需的表,并用初始值填充它们。%>cd mysql %>chown -R mysql . %>chgrp -R mysql . %>scripts/mysql_install_db --user=mysql %>chown -R root . %>chown -R mysql data
就这样!转到“设置 MySQL 管理员密码”一节。
源安装过程
MySQL 开发人员已经竭尽全力为各种操作系统开发优化的 rpm 和二进制文件,您应该尽可能地使用它们。但是,如果您正在使用一个不存在二进制文件的平台,需要一个特别奇特的配置,或者碰巧是一个相当有控制欲的人,那么您也可以选择从源代码安装。该过程只比二进制安装过程稍长一点。
也就是说,源代码安装过程确实比安装二进制文件或 rpm 要复杂一些。首先,你至少应该掌握如何使用 GNU gcc
和make
这样的构建工具的基本知识,并且你应该在你的操作系统上安装它们。假设如果你选择不听从使用二进制文件的建议,你已经知道所有这些了。因此,只提供安装说明,没有相应的解释:
-
创建必要的组和所有者:
%>groupadd mysql %>useradd –g mysql mysql
-
将软件解压缩到目标目录。建议使用 GNU
gunzip
和tar
程序。%>cd /usr/src %>gunzip < /usr/src/mysql-VERSION.tar.gz | tar xvf - %>cd mysql-VERSION
-
配置、制作和安装 MySQL。需要一个 C++编译器和
make
程序。强烈推荐使用 GNUgcc
和make
程序的最新版本。请记住,OTHER-CONFIGURATION-FLAGS
是任何配置设置的占位符,它决定了 MySQL 服务器的几个重要特征,比如安装位置。由您来决定哪种旗帜最适合您的特殊需求。%>./configure –prefix=/usr/local/mysql [OTHER-CONFIGURATION-FLAGS] %>make %>make install
-
将示例 MySQL 配置(
my.cnf
)文件复制到其典型位置,并设置其所有权。这个配置文件的作用将在后面的“my.cnf 文件”一节中详细讨论。%>cp support-files/my-medium.cnf /etc/my.cnf %>chown -R mysql . %>chgrp -R mysql .
-
安装 MySQL 数据库。
mysql_install_db
是一个 shell 脚本,它登录到 MySQL 数据库服务器,创建所有必需的表,并用初始值填充它们。%>scripts/mysql_install_db --user=mysql
-
更新安装权限:
%>chown -R root . %>chown -R mysql data
就是这样!转到“设置 MySQL 管理员密码”一节。
在 Windows 上安装和配置 MySQL
随着历史上占主导地位的基于 Unix 的技术如 Apache Web server、PHP 和 MySQL 越来越受欢迎,开源产品在 Microsoft Windows server 平台上继续取得进展。此外,对于许多用户来说,Windows 环境为 web/数据库应用提供了一个理想的开发和测试平台,这些应用最终将迁移到 Linux 生产环境中。
在 Windows 上安装 MySQL
和 Linux 版本一样,MySQL 和 MariaDB 都可以安装在 Windows 系统上。Windows 以上任何版本都可以。这两个数据库都可以通过 MSI 安装文件安装。这不仅会安装和配置必要的文件,还会提示用户设置 root 密码并执行其他安全设置。
虽然可以从源代码安装,但不建议这样做。安装包负责安全设置,您不需要访问通常不安装在 Windows 系统上的编译器和其他构建工具。
从 MySQL ( https://dev.mysql.com/downloads/mysql/
)或 MariaDB ( https://mariadb.com/downloads/mariadb-tx
)下载 MSI 安装文件开始。基于两种产品的不同,这两个安装程序的工作方式略有不同。尽管它们有着相同的根源,但它们已经发展成包含不同的选项。
启动和停止 MySQL
MySQL 服务器守护进程通过位于 I NSTALL-DIR/bin
目录中的一个程序来控制。本节提供了在 Linux 和 Windows 平台上控制该守护进程的说明。
手动控制守护程序
尽管您最终希望 MySQL 守护进程与操作系统一起自动启动和停止,但是在配置和应用测试阶段,您通常需要手动执行这个过程。
在 Linux 上启动 MySQL
负责启动 MySQL 守护进程的脚本叫做mysqld_safe
,它位于INSTALL-DIR/bin
目录中。该脚本只能由拥有足够执行权限的用户启动,通常是root
或组mysql
的成员。以下是在 Linux 上启动 MySQL 的命令:
%>cd INSTALL-DIR
%>./bin/mysqld_safe --user=mysql &
请记住,除非您首先切换到INSTALL-DIR
目录,否则mysqld_safe
不会执行。此外,后面的&符号是必需的,因为您希望守护进程在后台运行。
mysqld_safe
脚本实际上是 mysqld 服务器守护进程的包装器,提供了直接调用 mysqld 所不具备的特性,比如运行时日志和出错时自动重启。您将在“配置 MySQL”一节中了解更多关于mysqld_safe
的内容。
在 Red Hat/CentOS 的现代版本中,服务器的启动和停止通常是通过 systemctl 这样的服务管理器来完成的。启动、停止和获取 MariaDB 状态的命令如下所示:
%>systemctl start mariadb
%>systemctl stop mariadb
%>systemctl status mariadb
在旧版本的 Red Hat/CentOS 和 Debian/Ubuntu 发行版上,您将需要 service 命令来启动和停止 MySQL 守护进程。
%>service mysql start
%> service mysql stop
%> service mysql status
在 Windows 上启动 MySQL
假设您遵循了前一节“在 Windows 上配置 MySQL”中的说明,那么 MySQL 已经启动并作为服务运行。您可以通过导航到您的服务控制台来启动和停止该服务,该控制台可以通过从命令提示符执行services.msc
来打开。
在 Linux 和 Windows 上停止 MySQL
虽然 MySQL 服务器守护程序只能由拥有执行mysqld_safe
脚本所需的文件系统权限的用户启动,但是拥有 MySQL 权限数据库中指定的适当权限的用户可以停止它。请记住,这个特权通常只留给 MySQL root
用户,不要与操作系统root
用户混淆!现在不要太担心这个;请理解 MySQL 用户不同于操作系统用户,试图关闭服务器的 MySQL 用户必须拥有足够的权限。第二十七章提供了对mysqladmin
以及其他 MySQL 客户端的适当介绍;第二十九章深入探讨了与 MySQL 用户和 MySQL 特权系统相关的问题。在 Linux 和 Windows 上停止 MySQL 服务器的过程如下:
shell>cd INSTALL-DIR/bin
shell>mysqladmin -u root -p shutdown
Enter password: *******
假设您提供了正确的凭证,您将返回到命令提示符,而不会收到成功关闭 MySQL 服务器的通知。如果尝试关闭失败,会提供相应的错误消息。
配置和优化 MySQL
除非另有说明,否则每次启动 MySQL 服务器守护进程时,MySQL 都会采用一组默认的配置设置。虽然默认设置可能适合只需要标准部署的用户,但您至少要知道哪些地方可以调整,因为这样的更改不仅会使您的部署更好地适应您的特定宿主环境,而且还会基于应用的行为特征极大地增强应用的性能。例如,一些应用可能是更新密集型的,提示您调整 MySQL 处理写/修改查询所需的资源。其他应用可能需要处理大量的用户连接,从而促使分配给新连接的线程数量发生变化。令人高兴的是,MySQL 是高度可配置的;正如您将在本章和后面的章节中了解到的,管理员有机会管理其操作的几乎每个方面。
本节介绍了影响 MySQL 服务器一般操作的许多配置参数。因为配置和优化对于维护一个健康的服务器(更不用说一个理智的管理员)是如此重要,所以在本书的剩余部分中经常会提到这个主题。
mysqld_safe 包装器
虽然前面提到的mysqld
确实是 MySQL 的服务守护进程,但是你实际上很少和它直接交互;相反,您可以通过名为mysqld_safe
的包装器与守护程序进行交互。当守护进程启动时,mysqld_safe
包装器增加了一些额外的安全相关的日志特性和系统完整性特性。鉴于这些有用的特性,mysqld_safe
是启动服务器的首选方式,尽管您应该记住它只是一个包装器,不应该与服务器本身混淆。
注意
从 RPM 或 Debian 包安装包含一些对systemd
的额外支持,所以mysqld_safe
没有安装在这些平台上。请使用my.cnf
配置文件,下一节将详细介绍。
实际上有数百个 MySQL 服务器配置选项供您使用,能够微调守护程序操作的几乎每个可以想到的方面,包括 MySQL 的内存使用、日志敏感度和边界设置,如最大并发连接数、临时表和连接错误等。如果您想查看所有可用选项的摘要,请执行:
%>INSTALL-DIR/bin/mysqld --verbose --help
下一节重点介绍几个更常用的参数。
MySQL 的配置和优化参数
本节介绍了几个基本的配置参数,这些参数在开始管理服务器时可能会有所帮助。但是首先花点时间回顾一下如何快速查看 MySQL 的当前设置。
查看 MySQL 的配置参数
在上一节中,您学习了如何调用 mysqld 来了解您可以使用哪些选项。要查看当前的设置,您需要执行mysqladmin
客户端,如下所示:
%>mysqladmin -u root -p variables
或者,您可以登录到 mysql 客户端并执行以下命令:
mysql>SHOW VARIABLES;
这样做会产生一个很长的变量设置列表,如下所示:
+---------------------------------+----------------------------+
| Variable_name | Value |
+---------------------------------+----------------------------+
| auto_increment_increment | 1 |
| auto_increment_offset | 1 |
| automatic_sp_privileges | ON |
| back_log | 50 |
| basedir | C:\mysql5\ |
| binlog_cache_size | 32768 |
| bulk_insert_buffer_size | 8388608 |
| . . . | |
| version | 5.1.21-beta-community |
| version_comment | Official MySQL binary |
| version_compile_machine | ia32 |
| version_compile_os | Win32 |
| wait_timeout | 28800 |
+---------------------------------+----------------------------+
226 rows in set (0.00 sec)
您可以使用LIKE
子句查看单个变量的设置。例如,要确定默认存储引擎设置,可以使用以下命令:
mysql>SHOW VARIABLES LIKE "table_type";
执行此命令会产生类似于以下内容的输出:
+---------------+--------+
| Variable_name | Value |
+---------------+--------+
| table_type | InnoDB |
+---------------+--------+
1 row in set (0.00 sec)
最后,您可以使用以下命令查看一些非常有趣的统计信息,如正常运行时间、处理的查询以及接收和发送的总字节数:
mysql>SHOW STATUS;
执行此命令会产生类似如下的输出:
+-----------------------------------+-----------+
| Variable_name | Value |
+-----------------------------------+-----------+
| Aborted_clients | 0 |
| Aborted_connects | 1 |
| Binlog_cache_disk_use | 0 |
| Binlog_cache_use | 0 |
| Bytes_received | 134 |
| Bytes_sent | 6149 |
| Com_admin_commands | 0 |
| . . . | |
| Threads_cached | 0 |
| Threads_connected | 1 |
| Threads_created | 1 |
| Threads_running | 1 |
| Uptime | 848 |
+-----------------------------------+-----------+
管理连接负载
一个调优的 MySQL 服务器能够同时处理多个连接。每个连接都必须由主 MySQL 线程接收并委托给一个新线程,这个任务虽然琐碎,但不是即时的。back_log 参数确定当这个主线程处理特别重的新连接负载时,允许排队的连接数。默认情况下,该值设置为 80。
请记住,您不能仅仅将它设置为一个非常高的值,并假设它会使 MySQL 运行得更高效。您的操作系统和 web 服务器可能都有其他最大值设置,这些设置可能会使特别高的值变得无关紧要。
设置数据目录位置
常见的做法是将 MySQL 数据目录放在非标准位置,比如另一个磁盘分区。使用datadir
选项,您可以重新定义该路径。常见的做法是将第二个驱动器挂载到一个目录中,例如/data
,并将数据库存储在一个名为mysql
的目录中:
%>./bin/mysqld_safe --datadir=/data/mysql --user=mysql &
请记住,您需要将 MySQL 权限表(存储在DATADIR/mysql
中)复制或移动到这个新位置。因为 MySQL 的数据库是存储在文件中的,所以你可以通过使用操作系统命令来执行这样的操作,比如mv
和cp
。如果您使用的是 GUI,您可以将这些文件拖放到新位置。
设置默认存储引擎
正如你将在第二十八章中了解到的,MySQL 支持几种表格引擎,每一种都有自己的优缺点。如果您经常使用某个特定的引擎(默认为 InnoDB),您可能希望通过使用--default-storage-engine
参数将其设置为默认引擎。例如,您可以将默认值设置为 MEMORY,如下所示:
%>./bin/mysqld_safe --default-table-type=memory
一旦分配了内存引擎,所有后续的表创建查询都将自动使用内存引擎,除非另外指定。
自动执行 SQL 命令
您可以在守护程序启动时执行一系列 SQL 命令,方法是将它们放在一个文本文件中,并将该文件名分配给init_file
。假设您想在 MySQL 服务器每次启动时清除一个用于存储会话信息的表。将以下查询放在名为mysqlinitcmds.sql
的文件中:
DELETE FROM sessions;
然后,在执行| mysqld_safe
时,像这样分配init_file
:
%>./bin/mysqld_safe --init_file=/usr/local/mysql/scripts/mysqlinitcmds.sql &
记录潜在的非最佳查询
log-queries-not-using-indexes参数定义了一个文件,其中记录了所有不使用索引的查询。定期查看这些信息有助于发现对查询和表结构的可能改进。
记录慢速查询
log_slow_queries 参数定义了一个文件,该文件记录了执行时间超过 long_query_time 秒的所有查询。每当查询执行时间超过这个限制时, log_slow_queries 计数器就递增。使用mysqldumpslow
实用程序研究这样的日志文件对于确定数据库服务器中的瓶颈非常有用。
设置最大允许同时连接数
max_connections 参数决定了允许的最大并发数据库连接数。默认情况下,该值设置为 151。您可以通过检查 max_used_connections 参数来检查数据库同时打开的最大连接数,该参数可通过执行 SHOW STATUS 获得。如果你看到这个数字接近世纪标志,考虑提高最大值。请记住,随着连接数量的增加,内存消耗也会增加,因为 MySQL 会为它打开的每个连接分配额外的内存。
设置 MySQL 的通信端口
默认情况下,MySQL 在端口 3306 上通信;但是,您可以使用port
参数将其重新配置为监听任何其他端口。
禁用 DNS 解析
启用 skip-name-resolve 参数会阻止 MySQL 解析主机名。这意味着授权表中的所有Host
列值都由 IP 地址或本地主机组成。如果您计划只使用 IP 地址或本地主机,请启用此参数。在尝试连接之前,DNS 查找会将主机名转换为 IP 地址。启用此选项将禁用查找,只允许 IP 地址工作。主机名 localhost 是一个特例,它总是解析为本地 ip 地址(对于 IVv4 是 127.0.0.1)。
限制与本地服务器的连接
启用 skip-networking 参数可以防止 MySQL 监听 TCP/IP 连接,并使用 UNIX 套接字。这将阻止对服务器的远程访问,而无需配置特殊的防火墙规则。
设置 MySQL 守护进程用户
MySQL 守护进程应该作为非root
用户运行,从而在攻击者通过 MySQL 安全漏洞成功进入服务器时将损害降到最低。尽管通常的做法是以用户mysql
的身份运行服务器,但是您可以以任何现有用户的身份运行它,只要该用户是数据目录的所有者。例如,假设您想使用用户mysql
运行守护进程:
%>./bin/mysqld_safe --user=mysql &
my.cnf 文件
您已经了解到,在通过包装器mysqld_safe
启动 MySQL 守护进程时,可以在命令行上进行配置更改。然而,有一种更方便的方法来调整许多 MySQL 客户端的启动参数和行为,包括mysqladmin
、myisamchk
、myisampack
、mysql
、mysqlcheck
、mysqld
、mysqldump
、mysqld_safe
、mysql.server
、mysqlhotcopy
、mysqlimport
和mysqlshow
。您可以在 MySQL 的配置文件my.cnf
中维护这些调整。
在启动时,MySQL 在几个目录中查找my.cnf
文件,每个目录决定了其中声明的参数的范围。这里突出显示了每个目录的位置和相对范围:
-
/etc/my.cnf
(Windows 上的C:\my.cnf
或windows-sys-directory\my.ini
):全局配置文件。位于服务器上的所有 MySQL 服务器守护进程首先引用这个文件。请注意的扩展名。如果选择将配置文件放在 Windows 系统目录中,请使用。 -
DATADIR/my.cnf
:服务器特定配置。该文件位于服务器安装引用的目录中。这个配置文件的一个有点奇怪但却很重要的特征是,它只引用在配置时指定的数据目录,即使在运行时指定了新的数据目录。注意 MySQL 的 Windows 发行版不支持这个特性。 -
--defaults-extra-file=
名称:由提供的文件名指定的文件,包括绝对路径。 -
~/.my.cnf
:用户特定配置。该文件应该位于用户的主目录中。注意 MySQL 的 Windows 发行版不支持这个特性。
您应该明白,MySQL 在启动时会尝试从这些位置中的每一个读取数据。如果存在多个配置文件,后面读入的参数优先于前面读入的参数。虽然您可以创建自己的配置文件,但是您应该基于五个预配置的my.cnf
文件中的一个来创建您的文件,所有这些文件都随 MySQL 发行版一起提供。这些模板位于 INSTALL-DIR/support-files 中(在 Windows 上,这些文件位于安装目录中)。每个的目的在表 23-1 中定义。
表 23-1
MySQL 配置模板
|名字
|
描述
|
| — | — |
| my-huge.cnf
| 面向高端生产服务器,包含 1 至 2GB RAM,主要用于运行 MySQL |
| my-innodb-heavy-4G.cnf
| 适用于仅安装高达 4GB RAM 的 InnoDB,涉及大量查询和低流量 |
| my-large.cnf
| 面向中型生产服务器,包含大约 512MB RAM,主要用于运行 MySQL |
| my-medium.cnf
| 适用于包含少量内存(小于 128MB)的低端生产服务器 |
| my-small.cnf
| 适用于最低配置的服务器,拥有额定 RAM(小于 64MB) |
那么这个文件看起来像什么呢?下面是 my-large.cnf 配置模板的部分清单:
# Example mysql config file for large systems.
#
# This is for large system with memory = 512M where the system runs mainly
# MySQL.
# The following options will be passed to all MySQL clients
[client]
#password = your_password
port = 3306
socket = /tmp/mysql.sock
# Here follows entries for some specific programs
# The MySQL server
[mysqld]
port = 3306
socket = /tmp/mysql.sock
skip-locking
key_buffer=256M
max_allowed_packet=1M
table_cache=256
sort_buffer=1M
record_buffer=1M
myisam_sort_buffer_size=64M
[mysqldump]
quick
max_allowed_packet=16M
[mysql]
no-auto-rehash
# Remove the next comment character if you are not familiar with SQL
#safe-updates
...
看起来相当简单,对吧?的确如此。配置文件实际上可以总结为三个简洁的要点:
-
注释以散列符号(
#
)开头。 -
变量的赋值与调用
mysqld_safe
时完全一样,只是它们没有以双连字符开头。 -
这些变量的上下文是通过在该节前面加上预期受益人来设定的,用方括号括起来。例如,如果您想要调整
mysqldump
的默认行为,您可以从以下内容开始:
[mysqldump]
然后使用相关的变量设置,如下所示:
quick
max_allowed_packet = 16M
假设此上下文,直到遇到下一个方括号设置。
配置 PHP 使用 MySQL
PHP 和 MySQL 社区长期以来保持着密切的关系。各自的技术就像一个豆荚里的两颗豌豆,面包和黄油,葡萄酒和奶酪…你明白了。MySQL 在 PHP 社区中的受欢迎程度从早期就很明显,这促使 PHP 开发人员将 MySQL 客户端库与发行版捆绑在一起,并在 PHP version 4 中默认启用扩展。
但是你不能只安装 PHP 和 MySQL,就指望它们自动地一起工作。你只需要再执行几个步骤,如下所述。
在 Linux 上重新配置 PHP
在 Linux 系统上,成功安装 MySQL 后,需要重新配置 PHP,这一次包括--with-mysqli[=DIR]
配置选项,指定 MySQL 安装目录的路径。构建完成后,重启 Apache,就大功告成了。
在 Windows 上重新配置 PHP
在 Windows 上,您需要做两件事来启用 PHP 对 MySQL 的支持。成功安装 MySQL 后,打开php.ini
文件并取消对以下行的注释:
extension=php_mysqli.dll
重启 Apache 或 IIS,你就可以开始同时使用 PHP 和 MySQL 了!
注意
无论平台如何,您都可以通过执行phpinfo()
函数来验证扩展是否被加载(参见第二章了解关于此函数的更多信息)。
摘要
本章为开始使用 MySQL 服务器做准备。您不仅学习了如何安装和配置 MySQL,还了解了如何优化安装以最适合您的管理和应用偏好。配置和优化问题将在本书的剩余部分根据需要重新讨论。
下一章将介绍 MySQL 的许多客户端,它们为与服务器的许多方面进行交互提供了一种便捷的方式。