crontab 是 Linux 平台设置计划任务的一个命令。它简单易用,系统已经自带了,不需要你去编译安装。下面就已 Ubuntu 10.10 来简单说明一下它的用法。
在Ubuntu 10.10 终端下输入命令 crontab --help 会出现以下提示:
crontab: invalid option -- '-'
crontab: usage error: unrecognized option
usage: crontab [-u user] file
crontab [-u user] { -e | -l | -r } //命令格式
(default operation is replace, per 1003.2)
-e (edit user's crontab) //编辑用户下的计划任务
-l (list user's crontab) //列出用户下的所有计划任务
-r (delete user's crontab) //删除用户下的所有计划任务
-i (prompt before deleting user's crontab) //在删除计划任务之前弹出提醒
有了上述使用帮助,
我们可先输入:crontab -l 查看下当前用户下的所有计划任务;
再输入:crontab -e 来编辑计划任务,
如果是第一次使用该命令,系统或提示几种编辑器供选择,
选择[3] vim 即可,使用我们常用的vim编辑器来编辑计划任务。
选择之后会打开vim,在最后一行有一句注释:
# m h dom mon dow command
意思是:#分 时 日 月 周 执行命令
前面五个参数格式为:
* 表示任意取值范围内数字 在分钟里*表示0到59的任意数字
- 表示选择区域范围内的数字 3-10表示3-10的任意数字,包含3和10
, 选择数字 2,4,5 选择2,4,5
/ 每多少数字 在分钟里 */2表示每二分钟
所以,如我们想设定一个每5分钟执行的命令,
则输入一行:*/5 * * * * command
2012年9月27日星期四
PHP 调试工具Xdebug
1.Xdebug介绍
Xdebug是一个开放源代码的PHP程序调试器(即一个Debug工具),可以用来跟踪,调试和分析PHP程序的运行状况。
2.Xdebug安装及配置
1、下载Xdebug组件
到Xdebug官方网站 www.xdebug.org下载相对应的PHP版本的xdebug组件。如果你用的是 wamp ,则已经集成到里面了,可以用 phpinfo() 进行查看 Xdebug是否已经安装。![]() |
查看Xdebug是否已经安装 |
2、在PHP.ini中载入xdebug组件
![]() |
Xdebug在 php.ini 中的配置 |
更多的配置选项可以查看这里:Xdebug的配置选项
3、简单应用
<?phpinclude 'blogger.php';//因为没有这个文件,所以Xdebug会报下面的错误
?>
2012年9月26日星期三
PHP5中使用PDO连接数据库
1.什么是PDO?
PDO(PHP Data Objects) 是 PHP 的一个扩展,定义了一系列轻量级的、通用性的、跨数据库的访问接口。
在以前,如果你用的是MySQL数据库,要打开 php_mysql.dll 的一个扩展,然后用 PHP 提供的 MySQL 函数来访问数据库;如果你用的是 MSSQL,就打开 php_mssql.dll 的扩展,用 PHP 提供的 MSSQL 函数来访问数据库。现在,你只要打开 pdo 相应的数据库扩展(例如:在Windows 平台 PHP 5.3.5 的 php.ini 中 php_pdo_mysql.dll,php_pdo_mssql.dll),就能用 PDO 提供的各种方法来访问各种不同类型的数据库,如MySQL、Oracle、MSSQL。
PDO 是 PHP 5.1 新加入的,在 PHP 5.0 中 PDO 也能作为 PECL 的一个扩展来用,但是它不适用于 PHP 5.0 的早期版本。
它有点类似Java框架的Hibernate。
2.基本例子
![]() |
employees表 |
<?php
/* Connect to an ODBC database using driver invocation */
$dsn = 'mysql:dbname=test;host=127.0.0.1';
$user = 'root';
$password = 'root';
try {
$dbh = new PDO($dsn, $user, $password);
} catch (PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
}
$sth = $dbh->query('SELECT * FROM employees');//query方法用于查询
$result = $sth->fetch();//获取第一行数据
print_r($result);
$result = $sth->fetchAll();//获取所有数据
print_r($result);
?>
<?php
/* Connect to an ODBC database using driver invocation */
$dsn = 'mysql:dbname=test;host=127.0.0.1';
$user = 'root';
$password = 'root';
try {
$dbh = new PDO($dsn, $user, $password);
} catch (PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
}
//插入数据,exec方法用于 INSERT,UPDATE,DELETE等操作
$count = $dbh->exec("INSERT INTO employees (`id`,`fname`,`lname`,`hired`,`separated`,`job_code`,`store_id`)
VALUES ('5','sherlock','wang','2012-01-01','2013-03-01','10','20')");
print("affected $count rows.\n");
?>
<?php
/* Connect to an ODBC database using driver invocation */
$dsn = 'mysql:dbname=test;host=127.0.0.1';
$user = 'root';
$password = 'root';
try {
$dbh = new PDO($dsn, $user, $password);
} catch (PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
}
$sth = $dbh->prepare('SELECT * FROM employees WHERE job_code=:job_code AND store_id=:store_id');//prepare方法用於 SELECT、INSERT、UPDATE 及 DELETE 等需要多次進行資料處理的 SQL 上
$sth->execute(array(':job_code' => 2, ':store_id' => '2'));
$result = $sth->fetchAll();//获取所有数据
print_r($result);
$sth->execute(array(':job_code' => 12, ':store_id' => '7'));
$result = $sth->fetchAll();//获取所有数据
print_r($result);
?>
<?php
/* Connect to an ODBC database using driver invocation */
$dsn = 'mysql:dbname=test;host=127.0.0.1';
$user = 'root';
$password = 'root';
try {
$dbh = new PDO($dsn, $user, $password);
} catch (PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
}
$sth = $dbh->prepare('SELECT * FROM employees WHERE job_code=? AND store_id=?');//用 ? 代替
$sth->execute(array(2,'2'));//按 ? 出现的次序设置值
$result = $sth->fetchAll(PDO::FETCH_ASSOC);
print_r($result);
$sth->execute(array(12,'7'));
$result = $sth->fetchAll(PDO::FETCH_NUM);
print_r($result);
?>
详情:PDO API
2012年9月24日星期一
SVN 版本控制
1、什么是SVN(Subversion)
SVN,即Subversion,是一个自由开源的版本控制系统,可以将数据恢复到早期版本,或者检查数据修改的历史,这些数据可以是源代码,也可以是其他类型的文件。
可以简单理解为一个版本控制器,主要实现多人协作开发文件共享和版本控制的一个过程。
特点:
- 根据文件修改的次数自动增加版本号以便找回历史文件
- 防止多人开发时同时修改一类文件导致文件覆盖或被删除
- 及时更新和获取其他开发者的最新数据和文件
- 有助于项目开发时的安全,责任细分化
2、结构
- 服务器
Tigris svn
- 客户端
Tortoise svn3.服务器搭建
1.下载并安装tigris svn server 1.6.62.创建中心库
1.svnadmin create D:\SvnRepository #SVN 创建仓库
3.修改权限配置
1.修改 SvnRepository\conf\svnserve.conf # SvnRepository(仓库的名称) 的配置文件
2.修改 SvnRepository\conf\passwd # SvnRepository(仓库的名称) 客户端用户的访问配置文件
服务器运行方式
- svnserve
- apache http
4.运行
svnadmin help #SVN 管理帮助svnserve -d -r D:/SvnRepository # 启动SVN在后台以root的权限运行 不能关掉DOS命令控制台
5.备份
在 Subversion 中,有三种备份方式:完全备份、增量备份和版本库同步
(1)完全备份
命令格式:svnadmin hotcopy 源备份目录 目标备份目录
svnadmin hotcopy D:\svnroot\bbs D:\svnrootbak\bbs
(2)增量备份
当版本库数量非常多或非常大,或者不经常变动时,频繁地进行完全备份即浪费资源,也会影响到其他工作的效率,是完全没必要的。我们既想备份最新的版本库,又不想备份整个版本库,该怎么办呢?Subversion 为我们提供了一个只备份增量数据的命令,它通常与完全备份结合来使用。增量备份可以记录每次 Subversion 的提交变化,如果出现问题,也可以通过备份文件恢复到最新的可用状态。下面是使用 svnadmin dump 命令进行增量备份的示例:
svnadmin dump D:\svnroot\shop --revision 5 --incremental > D:\svnrootbak\dump\shop.dump
命令格式:svnadmin dump 源备份目录 --revision 版本号 --incremental > 目标备份目录
注意:如果不加参数 --incremental,后面执行 load 时会提示 “版本库已存在”。参数 --revision 用于指定要备份的版本。
Subversion 提供了一个钩子(hook)特性,使用它可以记录每次提交的结果。在版本库的目录结构中,hooks 目录里存放的就是钩子脚本,在这里我们只使用 post-commit 钩子,它会在每次提交之后执行。为了实现增量备份功能,我们在 hooks 目录下创建一个名为 post-commit.bat 文件,内容如下:
echo off
set SVN_HOME="D:\subversion"
set SVN_ROOT=D:\svnroot
set DELTA_BACKUP_SVN_ROOT=D:\svnrootbak\delta
set LOG_FILE=%1\backup.log
echo backup revision %2 >> %LOG_FILE%
for /r %SVN_ROOT% %%I in (.) do if D:\svnroot\%%~nI == %1 %SVN_ROOT%\%%~nI\hooks\deltaBackup.bat %%~nI %2
goto end
:end
通过这个脚本,可以实现 D:\svnroot 下的版本库在提交时自动将增量数据备份到 D:\svnrootbak\delta(确定这个目录存在)目录下。脚本 deltaBackup.bat 是对svnadmin dump 命令的封装,可以放在任何地方,其内容如下:
@echo 正在备份版本库%2......
%SVN_HOME%\bin\svnadmin dump %SVN_ROOT%\%1 --incremental --revision %2 >> %DELTA_BACKUP_SVN_ROOT%\%1.dump
@echo 版本库%2成功备份到了%3!
注意:如果不加参数 --incremental,后面执行 load 时会提示 “版本库已存在”。参数 --revision 用于指定要备份的版本。
Subversion 提供了一个钩子(hook)特性,使用它可以记录每次提交的结果。在版本库的目录结构中,hooks 目录里存放的就是钩子脚本,在这里我们只使用 post-commit 钩子,它会在每次提交之后执行。为了实现增量备份功能,我们在 hooks 目录下创建一个名为 post-commit.bat 文件,内容如下:
echo off
set SVN_HOME="D:\subversion"
set SVN_ROOT=D:\svnroot
set DELTA_BACKUP_SVN_ROOT=D:\svnrootbak\delta
set LOG_FILE=%1\backup.log
echo backup revision %2 >> %LOG_FILE%
for /r %SVN_ROOT% %%I in (.) do if D:\svnroot\%%~nI == %1 %SVN_ROOT%\%%~nI\hooks\deltaBackup.bat %%~nI %2
goto end
:end
通过这个脚本,可以实现 D:\svnroot 下的版本库在提交时自动将增量数据备份到 D:\svnrootbak\delta(确定这个目录存在)目录下。脚本 deltaBackup.bat 是对svnadmin dump 命令的封装,可以放在任何地方,其内容如下:
@echo 正在备份版本库%2......
%SVN_HOME%\bin\svnadmin dump %SVN_ROOT%\%1 --incremental --revision %2 >> %DELTA_BACKUP_SVN_ROOT%\%1.dump
@echo 版本库%2成功备份到了%3!
把以上两个脚本复制到其它版本库的 hooks 目录下,不需要做任何修改就可以实现增量数据的自动备份。
注意:完全备份和增量备份结合使用时,在完全备份之后一定要处理增量备份文件,使其只保存完全备份后的增量信息
如果版本库出现问题,我们可以通过备份文件恢复操作。使用 svnadmin load 命令来恢复以前的版本,命令如下:
svnadmin load D:\svnrootbak\shop < D:\svnrootbak\dump\shop.dump
命令格式:svnadmin load 最新完全备份文件目录 < 增量备份文件目录
(3)版本库同步
svnsync.exe 是在 Subversion 1.4 中增加的版本库同步工具,通过它可以实现一个版本库与另一个版本库的同步(同步是单向的),我们可以通过该功能达到备份版本库的目的。
首先在 svn 根目录下面创建一个 newshop 的版本库,用于同步已存在的 shop 版本库,然后使用 svnsync init 命令进行初始化,建立同步版本库之间的关系。例如:
svnsync init svn://localhost/newshop svn://localhost/shop [--username username --password password]
命令格式:svnsync init 目标版本库URL 源版本库URL [--username 用户名 --password 密码]
其中的 username 是具有访问目标版本库权限的用户。
注意:目标版本库必须是一个空的版本库,还需要在目标版本库 newshop 的 hooks 目录下新建一个名为 pre-revprop-change.bat 的文件,内容为空即可,否则会提示错误信息:
“svnsync:此版本库不允许修改版本属性;请管理员创建 pre-revprop-change 钩子”
接着,使用 synsync sync 命令来实现目标版本库与源版本库的真正同步,例如:
svnsync sync svn://localhost/newshop
命令格式:svnsync sync 目标版本库URL
执行完上面的命令后,在 newshop 对应的目录下右键单击 SVN 更新(update),会发现同步前在 shop 下提交的内容都会被更新。如果 shop 下有新提交内容,在 newshop 下是无法捕捉到的,需要重新执行 svnsync sync 命令,再次进行更新。
注意:目标版本库 newshop 只能是只读的,如果有文件发生变更,则无法继续同步了。还可以通过 svnsync 的子命令同步版本属性,例如:
svnsync copy-revprops svn://localhost/newshop
命令格式:svnsync copy-revprops 目标版本库URL
我们也可以使用 Subversion 的钩子机制来实现每次提交的自动同步,从而解决每次手动同步带来的麻烦。在源版本库 (shop)的 hooks 目录下创建一个名为 post-commit.bat 的文件,内容如下所示:
echo off
set SVN_HOME="D:\subversion"
%SVN_HOME%\bin\svnsync sync --non-interactive svn://localhost/newshop
如果源版本库执行了相关修改,在更新目标版本库时也会有同样的修改。但是,如果目标版本库设置了访问权限,每次源版本库修改时就会提示“钩子post-commit失败(退出代码1)输出”错误,解决办法是在 synsync 命令后面最加 --username 和 --password属性,例如:
%SVN_HOME%\bin\svnsync sync --non-interactive svn://localhost/newshop --username ccc -password 333333
JavaScript正则表达式和模式匹配
元字符
+ :元字符规定其前导字符必须在目标对象中连续出现一次或多次。* :元字符规定其前导字符必须在目标对象中出现零次或连续多次。
? :元字符规定其前导对象必须在目标对象中连续出现零次或一次。
\s :用于匹配单个空格符,包括tab 键和换行符。
\S :用于匹配除单个空格符之外的所有字符。
\d :用于匹配从0 到9 的数字。
\w :用于匹配字母,数字或下划线字符。
\W :用于匹配所有与\w 不匹配的字符。
(说明:我们可以把\s 和\S 以及\w 和\W 看作互为逆运算。同样,我们也可以把“^”和“$”以及“\b”和“\B”看作是互为逆运算的两组定位符)
定位符
^ :定位符规定匹配模式必须出现在目标字符串的开头。$ :定位符规定匹配模式必须出现在目标对象的结尾。
\b :定位符规定匹配模式必须出现在目标字符串的开头或结尾的两个边界之一。
\B :定位符则规定匹配对象必须位于目标字符串的开头和结尾两个边界之内,即匹配对象既不能作为目标字符串的开头,也不能作为目标字符串的结尾。
/fo+/
因为上述正则表达式中包含“+”元字符,表示可以与目标对象中的 “fool”, “fo”, 或者
“football”等在字母f 后面连续出现一个或多个字母o 的字符串相匹配。
/eg*/
因为上述正则表达式中包含“*”元字符,表示可以与目标对象中的 “easy”, “ego”, 或者 “egg”等在字母e 后面连续出现零个或多个字母g 的字符串相匹配。
/Wil?/
因为上述正则表达式中包含“?”元字符,表示可以与目标对象中的 “Win”, 或者 “Wilson”,等在字母i 后面连续出现零个或一个字母l 的字符串相匹配。
/^hell/
因为上述正则表达式中包含“^”定位符,所以可以与目标对象中以 “hell”, “hello”或
“hellhound”开头的字符串相匹配。
/ar$/
因为上述正则表达式中包含“$”定位符,所以可以与目标对象中以 “car”, “bar”或 “ar” 结尾的字符串相匹配。
/\bbom/
因为上述正则表达式模式以“\b”定位符开头,所以可以与目标对象中以 “bomb”, 或 “bom”开头的字符串相匹配。
/man\b/
因为上述正则表达式模式以“\b”定位符结尾,所以可以与目标对象中以 “human”, “woman”或“man”结尾的字符串相匹配。
字符类(“^”、“-”、“\”)
/[^A-C]/上述字符串将会与目标对象中除A,B,和C 之外的任何字符相匹配。一般来说,当“^”出现在 “[]”内时就被视做否定运算符;而当“^”位于“[]”之外,或没有“[]”时,则应当被视做定位符。
[A-Za-z0-9!#&*+=?_-] 匹配模式 “[]” 里面的特殊字符(*+?)并不是元字符
/Th\*/
上述正则表达式将会与目标对象中的“Th*”而非“The”等相匹配。反斜杠“\”表示转义字符序列,比如“\*”表示字符*,“\n”表示换行。
下列式子表示可以匹配3 个数字或者4 个小写字母:
var filter =/\d{3}|[a-z]{4}/;
还有一个'|',表示"或"操作:
"hi|hello":表示一个字符串里有"hi"或者"hello";
"(b|cd)ef":表示"bef"或"cdef";
"(a|b)*c":表示一串"a""b"混合的字符串后面跟一个"c";
'.'可以替代任何字符:
"a.[0-9]":表示一个字符串有一个"a"后面跟着一个任意字符和一个数字;
"^.{3}$":表示有任意三个字符的字符串(长度为3个字符);
上述正则表达式规定字符m 可以在匹配对象中连续出现2-6 次,因此,上述正则表达式可以同jimmy 或jimmmmmy 等字符串相匹配。
/Th\*/
上述正则表达式将会与目标对象中的“Th*”而非“The”等相匹配。反斜杠“\”表示转义字符序列,比如“\*”表示字符*,“\n”表示换行。
下列式子表示可以匹配3 个数字或者4 个小写字母:
var filter =/\d{3}|[a-z]{4}/;
还有一个'|',表示"或"操作:
"hi|hello":表示一个字符串里有"hi"或者"hello";
"(b|cd)ef":表示"bef"或"cdef";
"(a|b)*c":表示一串"a""b"混合的字符串后面跟一个"c";
'.'可以替代任何字符:
"a.[0-9]":表示一个字符串有一个"a"后面跟着一个任意字符和一个数字;
"^.{3}$":表示有任意三个字符的字符串(长度为3个字符);
匹配模式
/jim{2,6}/上述正则表达式规定字符m 可以在匹配对象中连续出现2-6 次,因此,上述正则表达式可以同jimmy 或jimmmmmy 等字符串相匹配。
运算顺序优先级
依然遵循从左到→右的运算规则
( ) 圆括号因为是内存处理所以最高
* ? + { } 重复匹配内容其次
^ $ \b 边界处理第三
| 条件处理第四
最后按照运算顺序计算匹配
补充:JavaScript RegExp 对象参考手册
2012年9月23日星期日
Joomla升级时出现JFolder::create: Could not create directory Warning: Failed to move file!
今天在把Joomla 2.5.6 升级到 2.5.7版本的时候出现以下错误。
这个错误会出现是因为在把站点从一台主机移到另外一台主机上。而忘记修改配置文件 configuration.php 里面的 $log_path 和 $tmp_path 选项了。
只要把 $log_path 和 $tmp_path 修改成正确的路径就可以了。
你可以用 phpinfo() 来查看 DocumentRoot 的路径,然后做相应的修改。
这个错误会出现是因为在把站点从一台主机移到另外一台主机上。而忘记修改配置文件 configuration.php 里面的 $log_path 和 $tmp_path 选项了。
只要把 $log_path 和 $tmp_path 修改成正确的路径就可以了。
你可以用 phpinfo() 来查看 DocumentRoot 的路径,然后做相应的修改。
MySQL5.5 分区简述
随着数据库应用规模的扩展,需要管理的数据规模也越来越大,普通的数据库查询优化机制在某些情况下已不能再满足性能的要求了。利用数据库分区技术,可以有效地减少 I/O 的数量,提升系统的查询性能。
分区(Partitioning)是数据库优化的一种方式。简单来说就是将同一数据表中的记录通过特定的算法进行分离,分别保存在不同的数据表中,甚至可以部署在不同的物理磁盘上,分散对同一张数据表的操作,提高访问的速度。
举个例子:db_blog 数据库存储了用户发表的博客内容,主要数据都在tb1_posts表中,它的结构如下所示:
我们按照 user_id%10 的规则将它分以下为10个数据表:
tb1_posts_0
tb1_posts_1
tb1_posts_2
tb1_posts_3
tb1_posts_4
tb1_posts_5
tb1_posts_6
tb1_posts_7
tb1_posts_8
tb1_posts_9
这样,当tb1_posts表的记录数目是上百万级时,它实际上是分布在10个分区表中,每一张表的记录数保持在相对较少的数量,有利于减少查询的时间,避免对同一张表的频繁读写,从而为数据库减少不必要的开销。
分区主要有两种形式:水平分区与垂直分区。
(1)水平分区
水平分区(Horizontal Partitioning)是对表的行进行分区,通过这样的方式使得不同分组里面的物理列分割的数据集得以组合,从而进行个体分割(单分区)或集体分割(一个或多个分区)。所有在表中定义的列在每个数据集中都能找到,所以表的特性依然得以保持。例如,一个包含10年发票记录的表可以分为10个不同的分区,每个分区都包含其中一年的记录(这里具体使用的分区方式一定要通过某个属性列来分割,譬如这里使用的列是年份)。
(2)垂直分区
垂直分区(Vertical Partitioning)是通过对表的垂直划分来减少目标表的宽度,使某些特定的列被划分到特定的分区,每个分区都包含了其中的列所对应的行。例如,一个包含了大 TEXT 和 BLOB 列的表,因为这些TEXT和BLOB列不经常被访问,所以这时就要把这些不经常使用的 TEXT 和 BLOB 列划分到另一个分区,以保证它们在数据库相关的同时还能提高访问速度。
这种算法是指按照分区索引字段的范围进行分区,比如我们可以将user_id为1~10000的记录存储在一个分区中,而将10001~20000的用户存储在另一个分区中,以此类推。
假设有20个音像店,分布在4个有经销权的地区,如下表所示:
用 List 分区语法如下:
它有4个分区,MySQL将通过用户函数(HASH(store_id))返回的结果的模数来确定使用哪个编号(N)的分区来保存每条记录。在上例中,N 是这样计算的:
哈希函数适用只对单个表列进行计算,并且它的值随列值进行一致地增大或减小,表达式值和它所基于的列的值变化越接近,MySQL就可以越有效地使用该表达式来进行HASH分区。
换句话说,如果列值与表达式值之比的曲线图越接近由等式"y=nx"(其中n为非零的常数)描绘出的直线,则该表达式越适合于哈希。这是因为,表达式的非线性越严重,分区中数据产生非均衡分布的趋势也将越严重。
按照线性哈希分区的优点在于增加、删除、合并和拆分分区将变得更加快捷,有利于处理含有极其大量(terabytes)数据的表。它的缺点在于,与使用常规HASH分区得到的数据分布相比,各个分区间数据的分布不大可能均衡。
通过以上可知,分区通过设置任意大小的规则,跨文件系统分配单个表的多个部分。实际上,表的不同部分在不同的位置被存储为单独的表。用户所选择的、实现数据分割的规则被称为分区函数。在MySQL中它可以是模数,或者是简单的匹配一个连续的数值区间或数值列表,或者是一个内部HASH函数,或一个线性HASH函数。函数根据用户指定的分区类型来选择,把用户提供的表达式的值作为参数。该表达式可以是一个整数列值,或一个作用在一个或多个列值上并返回一个整数的函数。这个表达式的值传递给分区函数,分区函数返回一个表示那个特定记录应该保存在哪个分区的序号。
很多时候,使用分区就不要再使用主键,否则可能影响性能。
只能通过 int 类型的字段或者返回 int 类型的表达式来分区。
每个表最多 1024 个分区,不可能无限制扩展分区,而且过度使用分区往往会消耗大量系统内存。
采用分区的表不支持外键,相关的约束逻辑必须通过程序来实现。
分区后,可能会造成索引失效,需要验证分区可行性。
1.什么是分区?
数据库分区是一种物理数据库设计技术,DBA 和数据库建模人员对其相当熟悉。虽然分区技术可以实现很多效果,但其主要目的是在特定的 SQL 操作中减少数据库读/写的总量以缩减响应的时间。分区(Partitioning)是数据库优化的一种方式。简单来说就是将同一数据表中的记录通过特定的算法进行分离,分别保存在不同的数据表中,甚至可以部署在不同的物理磁盘上,分散对同一张数据表的操作,提高访问的速度。
举个例子:db_blog 数据库存储了用户发表的博客内容,主要数据都在tb1_posts表中,它的结构如下所示:
我们按照 user_id%10 的规则将它分以下为10个数据表:
tb1_posts_0
tb1_posts_1
tb1_posts_2
tb1_posts_3
tb1_posts_4
tb1_posts_5
tb1_posts_6
tb1_posts_7
tb1_posts_8
tb1_posts_9
这样,当tb1_posts表的记录数目是上百万级时,它实际上是分布在10个分区表中,每一张表的记录数保持在相对较少的数量,有利于减少查询的时间,避免对同一张表的频繁读写,从而为数据库减少不必要的开销。
![]() |
数据表分布在不同的物理硬盘上(水平分区) |
分区主要有两种形式:水平分区与垂直分区。
(1)水平分区
水平分区(Horizontal Partitioning)是对表的行进行分区,通过这样的方式使得不同分组里面的物理列分割的数据集得以组合,从而进行个体分割(单分区)或集体分割(一个或多个分区)。所有在表中定义的列在每个数据集中都能找到,所以表的特性依然得以保持。例如,一个包含10年发票记录的表可以分为10个不同的分区,每个分区都包含其中一年的记录(这里具体使用的分区方式一定要通过某个属性列来分割,譬如这里使用的列是年份)。
(2)垂直分区
垂直分区(Vertical Partitioning)是通过对表的垂直划分来减少目标表的宽度,使某些特定的列被划分到特定的分区,每个分区都包含了其中的列所对应的行。例如,一个包含了大 TEXT 和 BLOB 列的表,因为这些TEXT和BLOB列不经常被访问,所以这时就要把这些不经常使用的 TEXT 和 BLOB 列划分到另一个分区,以保证它们在数据库相关的同时还能提高访问速度。
2.分区类型
- RANGE分区(RANGE Partitioning)
再引用官方的一个例子:创建 RANGE 分区表的语法
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT NOT NULL,
store_id INT NOT NULL
)
PARTITION BY RANGE (store_id) (
PARTITION p0 VALUES LESS THAN (6),
PARTITION p1 VALUES LESS THAN (11),
PARTITION p2 VALUES LESS THAN (16),
PARTITION p3 VALUES LESS THAN MAXVALUE
);
其中:p0、p1、p2、p3 表示分区的名称,MAXVALUE 表示最大的可能的整数值。
- List分区(LIST Partitioning)
这种算法将对分区索引字段的每个可能的结果创建一个分区映射关系,这个映射关系将会非常庞大。
地区 | 商店ID 号 |
北区 | 3, 5, 6, 9, 17 |
东区 | 1, 2, 10, 11, 19, 20 |
西区 | 4, 12, 13, 14, 18 |
中心区 | 7, 8, 15, 16 |
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT,
store_id INT
)
PARTITION BY LIST(store_id) (
PARTITION pNorth VALUES IN (3,5,6,9,17),--数据和索引放在默认的位置
PARTITION pEast VALUES IN (1,2,10,11,19,20)
DATA DIRECTORY = '/disk0/data'
INDEX DIRECTORY = '/disk0/idx',--把数据和索引放在不同的磁盘上
PARTITION pWest VALUES IN (4,12,13,14,18)
DATA DIRECTORY = '/disk1/data'
INDEX DIRECTORY = '/disk1/idx',--当然,也可以是不同的物理硬盘上,如果有多个物理硬盘的话
PARTITION pCentral VALUES IN (7,8,15,16)
DATA DIRECTORY = '/disk2/data'
INDEX DIRECTORY = '/disk2/idx'
);
- Hash分区(Hash Partitioning)
刚才我们通过 user_id%10 来实现分区便是这种算法,它非常容易实现。
例如,下面的语句创建了一个使用基于"store_id"列进行哈希处理的表,该表被分成了4个分区:
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULL DEFAULT '1970-01-01',
separated DATE NOT NULL DEFAULT '9999-12-31',
job_code INT,
store_id INT
)
PARTITION BY HASH(store_id)
PARTITIONS 4;
它有4个分区,MySQL将通过用户函数(HASH(store_id))返回的结果的模数来确定使用哪个编号(N)的分区来保存每条记录。在上例中,N 是这样计算的:MOD(7,4) -- 7表示的是 store_id = MOD(7,4) -- 取模运算 = 3 -- 表示第4个分区
哈希函数适用只对单个表列进行计算,并且它的值随列值进行一致地增大或减小,表达式值和它所基于的列的值变化越接近,MySQL就可以越有效地使用该表达式来进行HASH分区。
换句话说,如果列值与表达式值之比的曲线图越接近由等式"y=nx"(其中n为非零的常数)描绘出的直线,则该表达式越适合于哈希。这是因为,表达式的非线性越严重,分区中数据产生非均衡分布的趋势也将越严重。
LINEAR HASH分区(LINEAR HASH Partitioning)
线性哈希功能,它与常规哈希的区别在于,线性哈希功能使用的一个线性的2的幂(powers-of-two)运算法则,而常规哈希使用的是求哈希函数值的模数。按照线性哈希分区的优点在于增加、删除、合并和拆分分区将变得更加快捷,有利于处理含有极其大量(terabytes)数据的表。它的缺点在于,与使用常规HASH分区得到的数据分布相比,各个分区间数据的分布不大可能均衡。
CREATE TABLE employees ( id INT NOT NULL, fname VARCHAR(30), lname VARCHAR(30), hired DATE NOT NULL DEFAULT '1970-01-01', separated DATE NOT NULL DEFAULT '9999-12-31', job_code INT, store_id INT ) PARTITION BY LINEAR HASH( YEAR(hired) ) PARTITIONS 4;
通过以上可知,分区通过设置任意大小的规则,跨文件系统分配单个表的多个部分。实际上,表的不同部分在不同的位置被存储为单独的表。用户所选择的、实现数据分割的规则被称为分区函数。在MySQL中它可以是模数,或者是简单的匹配一个连续的数值区间或数值列表,或者是一个内部HASH函数,或一个线性HASH函数。函数根据用户指定的分区类型来选择,把用户提供的表达式的值作为参数。该表达式可以是一个整数列值,或一个作用在一个或多个列值上并返回一个整数的函数。这个表达式的值传递给分区函数,分区函数返回一个表示那个特定记录应该保存在哪个分区的序号。
3.分区的约束和限制
如果分区表使用的是
InnoDB
存储引擎,那么 DATA DIRECTORY
(数据目录)和 INDEX DIRECTORY
(索引目录)配置选项无效。
在 Windows 平台下,不管使用的是
InnoDB
存储引擎还是 MyISAM
存储引擎, DATA DIRECTORY
(数据目录)和 INDEX DIRECTORY
(索引目录)配置选项也是无效的。很多时候,使用分区就不要再使用主键,否则可能影响性能。
只能通过 int 类型的字段或者返回 int 类型的表达式来分区。
每个表最多 1024 个分区,不可能无限制扩展分区,而且过度使用分区往往会消耗大量系统内存。
采用分区的表不支持外键,相关的约束逻辑必须通过程序来实现。
分区后,可能会造成索引失效,需要验证分区可行性。