Nginx + PHP mysql_pconnect = Database errors (Too many connections)

If you’re using NGinx spawn-cgi or FPM with PHP and calling mysql_pconnect, you are likely going to experience frequent database crashes and “Too many connections” errors.

This took a while to trace, but once you understand the issue, it all makes sense.

mysql_pconnect opens a “persistent” connection to the database. From the documentation: “the connection to the SQL server will not be closed when the execution of the script ends. Instead, the link will remain open for future use (mysql_close() will not close links established by mysql_pconnect()).”

The issue is that FPM keeps a number of php-cgi processes running in the background to process php scripts. These php-cgi processes never die and so MySQL connections keep open forever…

Sooner or later, you are going to run out of MySQL connections (or worse yet – run out of file descriptors) and that’s when all hell breaks loose.

And if that’s not enough, after doing some digging into mysql_pconnect I found a few additional reasons NOT to use mysql_pconnect:

1. If you use mysql_pconnect on a machine that has a local database and you are connecting to a remote database, PHP will try to use the same mysql connection for both databases.

2. Temporary tables don’t work with persistent connections (they are only visible to the connection that was used to open the table)

3. Setting charset variables on a persistent connection, is going to impact all future queries on that connection as well

4. Calling mysql_pconnect twice (in the same script) with different parameters doesn’t work as expected

5. PHP 4.1 on Apache running with MySQL persistent connections, is known to memory leak (not flushing properly).

Bottom line, never ever use mysql_pconnect.

Replace all occurrences of mysql_pconnect with mysql_connect in your code and in your php.ini file, prevent persistent connections:

[MySQL] 
; Allow or prevent persistent links. 
mysql.allow_persistent = Off 

原文 http://www.softwareprojects.com/resources/programming/t-nginx-+-php-mysql_pconnect–database-errors-too-m-1751.html

Installing MySQL 5.1 on FreeBSD

This step by step tutorial is everything you need to install MySQL 5.1 (from source) on a virgin FreeBSD 4.x – 7.x machine.

Step 1: Install wget

cd /usr/ports/ftp/wget
make 
make install 

Step 2: Download MySQL 5.1 source code

mkdir /usr/tmp
cd /usr/tmp
wget "http://dev.mysql.com/get/Downloads/MySQL-5.1/mysql-5.1.24-rc.tar.gz/from/http://mysql.he.net/"
tar xvzf mysql-5.1.24-rc.tar.gz

Step 3: Create mysql user

adduser
Follow the prompts to create a mysql user and mysql group with a default directory of /usr/local/mysql

Step 4: Compile and Install MySQL with InnoDB support

cd /usr/tmp/mysql-5.1.24-rc
./configure --with-innodb
make all
make install

Step 5: Install default MySQL tables

cd /usr/local/bin
./mysql_install_db
cd /usr/local
chown -R mysql:mysql var

Step 6: Startup MySQL and connect to it

/usr/local/bin/mysqld_safe &
/usr/local/bin/mysql

Step 7: Set MySQL to start automatically when the machine reboots

Save the file below under /usr/local/etc/rc.d and call it mysqlstart.sh

/usr/local/bin/mysqld_safe &

Mark it as an executable

chmod 755 /usr/local/etc/rc.d/mysqlstart.sh

Step 8: Customizations

The following steps are optional.

By this point you should have MySQL installed and running on your new FreeBSD machine.

The customizations below are things that we do here at SoftwareProjects and are designed to get the most juice out of MySQL 5.1 in our environment.
All of our MySQL database machines run on FreeBSD 6/7 with a minimum of 4GB memory.

Shutdown the MySQL database

/usr/local/bin/mysqladmin shutdown

Move the data directory to /usr/local/mysql/data

mv /usr/local/var /usr/local/mysql/data
Save our custom MySQL configuration file under /etc/my.cnf

Note: Be sure to assign a unique server-id and note the auto-increment field – this is useful for master/master setups.

Step 9: Lift process size restrictions

FreeBSD limits process max size in memory to 512MB. We’re going to want to use more for MySQL.

To lift FreeBSD process size restrictions, we have to update /boot/loader.conf with:

sl_aacu_load="YES"
kern.maxdsiz="2073741824" # 2GB
kern.dfldsiz="2073741824" # 2GB
#kern.maxssiz="536870912" # 512MB

And then reboot the machine

Step 10: Populate database with data

Typically when we install a new database server, we want to populate it with data from another database.

The absolute easiest way to populate a secondary MySQL database machine with data from another MySQL database, is by using a script called InnoDBBackup by the makers of InnoDB.

Install both ibbackup and the Perl innobackup script on the master database machine (the one you are looking to transfer data from)

create a backup of the database (the great thing about this is you never have to take the database down):

./innobackup /etc/my.cnf /usr/local/mysql/databackup

Once the backup completes, transfer all the data over from the master to this new machine we are setting up –

On the new machine we are setting up, create a new directory to hold the backup:

mkdir /usr/local/mysql/databackup
chmod a+rw /usr/local/mysql/databackup

On the master machine, issue this tar command:

tar -czpf - /usr/local/mysql/databackup --exclude mysql | ssh -lUSERNAME NEWHOST.COM tar -xzpf - -C /usr/local/mysql/databackup

Once the tar is done, copy ibbackup and innobackup to the destination machine under /usr/tmp and run these two commands:

./innobackup --apply-log /etc/my.cnf /usr/local/mysql/databackup/2008-05-02_09-34-03/backup-my.cnf 

./innobackup --copy-back /etc/my.cnf /usr/local/mysql/databackup/2008-05-02_09-34-03/backup-my.cnf
Replace the 2008-05-02... with the name of your backup folder

Important: If you have a lot of databases, you may get “Too many open files” EMFILE error 24.

To fix this, you need to increase the number of file descriptors.

Update your /etc/sysctl.conf with the settings below and reboot the machine before retrying:

kern.maxfiles=65535
kern.maxfilesperproc=56384
kern.maxproc=50000

vfs.vmiodirenable=1
net.inet.tcp.msl=2000

net.inet.tcp.rfc1323=1
net.inet.tcp.delayed_ack=0
net.inet.tcp.restrict_rst=1
kern.ipc.maxsockbuf=2097152
kern.ipc.somaxconn=4096
kern.ipc.maxsockets=52328
net.inet.ip.portrange.first=20000

net.inet.ip.portrange.last=65535
net.inet.ip.portrange.hifirst=20000
net.inet.ip.portrange.hilast=65535

转自http://www.softwareprojects.com/resources/programming/t-installing-mysql-51-on-freebsd-1510.html

tar zxfk filename.tar.gz -k,–keep-old-files 不覆盖已存在文件

版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明。
http://windtear.net/archives/2005/09/22/000777.html

倒数据当然推荐rsync
不过当文件数超级多 或者网络条件等不具备时 tar 打包就是首选了

数据运到目的地后 解包时需要注意别覆盖

tar 工具的 -k 参数就提供干这事
-k,–keep-old-files 不覆盖已存在文件

-k, –keep-old-files
keep existing files; don’t overwrite them from archive

如:
tar zxfk filename.tar.gz

tar命令详解 (推荐)

tar [-cxtzjvfpPN] 文件与目录 …

参数:

-c :建立一个压缩文件的参数指令(create 的意思);
-x :解开一个压缩文件的参数指令!
-t :查看 tarfile 里面的文件!
特别注意,在参数的下达中, c/x/t 仅能存在一个!不可同时存在!因为不可能同时压缩与解压缩。

-z :是否同时具有 gzip 的属性?亦即是否需要用 gzip 压缩?
-j :是否同时具有 bzip2 的属性?亦即是否需要用 bzip2 压缩?
-v :压缩的过程中显示文件!这个常用,但不建议用在背景执行过程!
-f :使用档名,请留意,在 f 之后要立即接档名喔!不要再加参数!
例如使用『 tar -zcvfP tfile sfile』就是错误的写法,要写成『 tar -zcvPf tfile sfile』才对喔!

-p :使用原文件的原来属性(属性不会依据使用者而变)
-P :可以使用绝对路径来压缩!
-N :比后面接的日期(yyyy/mm/dd)还要新的才会被打包进新建的文件中!
–exclude FILE:在压缩的过程中,不要将 FILE 打包!
范例:

范例一:将整个 /etc 目录下的文件全部打包成为 /tmp/etc.tar

[root@linux ~]# tar -cvf /tmp/etc.tar /etc <==仅打包,不压缩! [root@linux ~]# tar -zcvf /tmp/etc.tar.gz /etc <==打包后,以 gzip 压缩 [root@linux ~]# tar -jcvf /tmp/etc.tar.bz2 /etc <==打包后,以 bzip2 压缩 # 特别注意,在参数 f 之后的文件档名是自己取的,我们习惯上都用 .tar 来作为辨识。 # 如果加 z 参数,则以 .tar.gz 或 .tgz 来代表 gzip 压缩过的 tar file ~ # 如果加 j 参数,则以 .tar.bz2 来作为附档名啊~ # 上述指令在执行的时候,会显示一个警告讯息: # 『tar: Removing leading `/’ from member names』那是关於绝对路径的特殊设定。 范例二:查阅上述 /tmp/etc.tar.gz 文件内有哪些文件? [root@linux ~]# tar -ztvf /tmp/etc.tar.gz # 由於我们使用 gzip 压缩,所以要查阅该 tar file 内的文件时, # 就得要加上 z 这个参数了!这很重要的! 范例三:将 /tmp/etc.tar.gz 文件解压缩在 /usr/local/src 底下 [root@linux ~]# cd /usr/local/src [root@linux src]# tar -zxvf /tmp/etc.tar.gz # 在预设的情况下,我们可以将压缩档在任何地方解开的!以这个范例来说, # 我先将工作目录变换到 /usr/local/src 底下,并且解开 /tmp/etc.tar.gz , # 则解开的目录会在 /usr/local/src/etc 呢!另外,如果您进入 /usr/local/src/etc # 则会发现,该目录下的文件属性与 /etc/ 可能会有所不同喔! 范例四:在 /tmp 底下,我只想要将 /tmp/etc.tar.gz 内的 etc/passwd 解开而已 [root@linux ~]# cd /tmp [root@linux tmp]# tar -zxvf /tmp/etc.tar.gz etc/passwd # 我可以透过 tar -ztvf 来查阅 tarfile 内的文件名称,如果单只要一个文件, # 就可以透过这个方式来下达!注意到! etc.tar.gz 内的根目录 / 是被拿掉了! 范例五:将 /etc/ 内的所有文件备份下来,并且保存其权限! [root@linux ~]# tar -zxvpf /tmp/etc.tar.gz /etc # 这个 -p 的属性是很重要的,尤其是当您要保留原本文件的属性时! 范例六:在 /home 当中,比 2005/06/01 新的文件才备份 [root@linux ~]# tar -N ‘2005/06/01′ -zcvf home.tar.gz /home 范例七:我要备份 /home, /etc ,但不要 /home/dmtsai [root@linux ~]# tar –exclude /home/dmtsai -zcvf myfile.tar.gz /home/* /etc 注意:经本人测试后发现相对用相对路径和绝对路径有不同的结果,请注意区分以下差别: #tar cvf aaa.tar --exclude='/export/home/ipnet/tools/old/tools.client' old old/ old/tools.client/ old/tools.client/conf.ini old/tools.client/tools.jar old/tools.client/fping old/tools.client/run.sh old/tools.client/fping.linux old/tools.client/nohup.out old/tools.server/ old/tools.server/run.sh old/tools.server/startserver old/tools.server/tools.jar old/tools.server/nohup.out -------------------------------------------------------------- #tar cvf bbb.tar --exclude='tools.client' old old/ old/tools.server/ old/tools.server/run.sh old/tools.server/startserver old/tools.server/tools.jar old/tools.server/nohup.out #tar cvf aaa.tar --exclude='tools.client' old old/ old/tools.server/ old/tools.server/run.sh old/tools.server/startserver old/tools.server/tools.jar old/tools.server/nohup.out old/aaa.tar #tar cvf aaa.tar --exclude='tools.client/' old old/ old/tools.client/ old/tools.client/conf.ini old/tools.client/tools.jar old/tools.client/fping old/tools.client/run.sh old/tools.client/fping.linux old/tools.client/nohup.out old/tools.server/ old/tools.server/run.sh old/tools.server/startserver old/tools.server/tools.jar old/tools.server/nohup.out old/aaa.tar 范例八:将 /etc/ 打包后直接解开在 /tmp 底下,而不产生文件! [root@linux ~]# cd /tmp [root@linux tmp]# tar -cvf - /etc | tar -xvf - # 这个动作有点像是 cp -r /etc /tmp 啦~依旧是有其有用途的! # 要注意的地方在於输出档变成 - 而输入档也变成 - ,又有一个 | 存在~ # 这分别代表 standard output, standard input 与管线命令啦! http://hi.baidu.com/netalang/blog/item/a0feca02ddb94dea08fa9309.html

cp 命令

cp [-R [-H | -L | -P]] [-f | -i | -n] [-alpv] source_file target_file
cp [-R [-H | -L | -P]] [-f | -i | -n] [-alpv] source_file … target_directory

-i 覆盖确认
-n 覆盖不确认
-v 显示详情
-R 全部目录
-p 权限和source_file 一样

其它的暂时没用到,先写到这里

FreeBSD常见问题集合

1.如何使用脚本获得PID
参见脚本pid.sh, 如下:

$ cat pid.sh
#!/bin/sh
pid=`ps -aux | grep $1 | grep -v grep | awk '{print $2}'`
echo $pid < $1.pid
$./pid.sh sshd

2. 将文件名从大写转换为小写

参见脚本mklower.sh, 如下:

#!/bin/sh
for file in $1; 
do
newfile=`echo "$file" | tr "[:upper:]" "[:lower:]"`
mv "$file" "$newfile"
done
$ mklower.sh *.JPG

3. 子目录按大小排序

$du -s * | sort -n

4. ls显示颜色

$ls -G #显示属性颜色 $ls -F #显示属性字符 $ls -FG

5. 快速安装package

你可以先设定 PACKAGEROOT,指定用该站点的package,安装时自动寻找对应pakage,而且你不需要知道版本,只要知道package名称即可,会自动安装package的最新版,中文的部分只要加上 zh-,像是 zh-xcin。

# setenv PACKAGEROOT ftp://ftp.tw.freebsd.org
# pkg_add -r package_name

也可以直接指定 PACKAGESITE 指向特定的位置. 像是你希望安装的package是 最新的, 而不是 release 的版本.

# setenv PACKAGESITE ftp://ftp.tw.freebsd.org/pub/Fre ... ages-stable/Latest/

6. 使用ISO镜像

FreeBSD 4.X

#vnconfig /dev/vn0c ./IMAGE.ISO
#mount -t cd9660 /dev/vn0c MOUNT_DIR 
#umount MOUNT_DIR
#vnconfig -u /dev/vn0c
FreeBSD 5.X

#mdconfig -a -t vnode -f abc.iso -u 1 
#mount -t cd9660 /dev/md1 MOUNT_DIR 
#umount MOUNT_DIR

7. tcsh, csh技巧

在tcsh中可以使用set autolist设置文件名和路径自动补齐

在csh中可以设置limit coredumpsize 0来设置coredump的大小

设置命令行提示符

set prompt = '%n@%m%#' 

如何将FreeBSD复制到另一颗硬盘 ?

如何将Linux或FreeBSD复制到另一颗硬盘 ?
====================================
  Linux上的系统复制很简单,使用cp -ax将partition资料复制过去,重开机後设定lilo就可以了。
FreeBSD也可以用cp来复制文档,但是对于复制整个文档系统并不是好方法。
这里介绍使用dump和restore来做:

创建新的文档系统
假如你的新硬盘为ad1, 而将来的根分区将是ad1s1a, 你可以先创建文档系统:
newfs /dev/ad1s1a
mount /dev/ad1s1a /mnt
cd /mnt

复制:
dump -f- / | restore -f- -r
这是把老的根文档系统复制输出到管道,restore从管道里读数据,写入当前目录所在的文档系统。

按部就班复制其他文档系统
复制完後,也许 要修改新硬盘下的/etc/fstab,安装上新的分区,摘下老硬盘就可以了。
再补充一点,如果你要复制的是另外一台机器,可以用rsh,这样就可以通过网络把一个分区数据传送到另外一台机器,不需要NFS,不需SAMBA等就可以解决问题,而cp就很难作到了。
newfs /dev/ad1s1a
mount /dev/ad1s1a /mnt
cd /mnt
rsh -l yourname thathost ‘dump -f- /’ | restore -f- -r

http://www.cnblogs.com/znuwcj/archive/2006/08/22/483583.aspx

fsck命令详解

功能说明:检查文件系统并尝试修复错误。
语  法:fsck [-aANPrRsTV][-t <文件系统类型>][文件系统…]
补充说明:当文件系统发生错误四化,可用fsck指令尝试加以修复。
参  数:
-a 自动修复文件系统,不询问任何问题。
-A 依照/etc/fstab配置文件的内容,检查文件内所列的全部文件系统。
-N 不执行指令,仅列出实际执行会进行的动作。
-P 当搭配”-A”参数使用时,则会同时检查所有的文件系统。
-r 采用互动模式,在执行修复时询问问题,让用户得以确认并决定处理方式。
-R 当搭配”-A”参数使用时,则会略过/目录的文件系统不予检查。
-s 依序执行检查作业,而非同时执行。
-t<文件系统类型> 指定要检查的文件系统类型。
-T 执行fsck指令时,不显示标题信息。
-V 显示指令执行过程。
例子 :
检查 msdos 档案系统的 /dev/hda5 是否正常,如果有异常便自动修复 :
fsck -t msdos -a /dev/hda5

注意 :

此指令可与 /etc/fstab 相互参考操作来加以了解。

运行fsck命令后产生的文件有什么用?

当执行fsck命令时,fsck命令如果发现存在孤立的文件或目录,这些孤立的文件或目录对于系统管理员或用户来说,无法访问到它,因为它与它的上级 目录失去了关联,如果用户允许fsck重新把它们找回来的话,fsck命令就会把这些孤立的文件或目录放在文件系统的/lost+found目录下,并用 各自的i-node号来命名,以便用户查找自己需要的文件。Lost+found目录通过它的英文含义我们都可以知道,它是一个失物认领处。

因此当某个用户发现自己丢失了某个文件,可以在执行fsck之后到/lost+found目录下去查找,这时通过文件名已无法辨认出文件的作用,只能 用file之类的命令来确定文件的类型,如果是数据文件,可以用more或vi命令来查看,如果是二进制文件,可以用dbx命令来调试或者试着执行它(注 意它可能是一个具有破坏性的程序),知道文件或目录的作用之后,可以对其进行改名。

如果用户不允许fsck把这些孤立的文件或目录找回来,那么fsck命令就会破坏这些文件或目录,彻底丢失这些文件或目录,用户或系统管理员永远也无法找回它们。

原文 http://hi.baidu.com/blvm/blog/item/7b7c95c46b61a6ad8226acf4.html

mysql explain详解

在做sql查询的时候,应该想到explain,这样用来对sql的执行效率进行分析。

mysql explain的详解
explain显示了mysql如何使用索引来处理select语句以及连接表。可以帮助选择更好的索引和写出更优化的查询语句。
使用方法,在select语句前加上explain就可以了:

如:explain select surname,first_name form a,b where a.id=b.id

分析结果形式如下:

table type possible_keys key key_len ref rows extra
a range id first_name First_name 9 NULL 23112 Using where Using temporary Using filesort
b ref id  first_name id 4 id 2 Using where

EXPLAIN列的解释:

table:显示这一行的数据是关于哪张表的
type:这是重要的列,显示连接使用了何种类型。从最好到最差的连接类型为const、eq_reg、ref、range、indexhe和ALL
possible_keys:显示可能应用在这张表中的索引。如果为空,没有可能的索引。可以为相关的域从WHERE语句中选择一个合适的语句
key:实际使用的索引。如果为NULL,则没有使用索引。很少的情况下,MYSQL会选择优化不足的索引。这种情况下,可以在SELECT语句中使用 USE INDEX(indexname)来强制使用一个索引或者用IGNORE INDEX(indexname)来强制MYSQL忽略索引
key_len:使用的索引的长度。在不损失精确性的情况下,长度越短越好
ref:显示索引的哪一列被使用了,如果可能的话,是一个常数
rows:MYSQL认为必须检查的用来返回请求数据的行数
Extra:关于MYSQL如何解析查询的额外信息。将在表4.3中讨论,但这里可以看到的坏的例子是Using temporary和Using filesort,意思MYSQL根本不能使用索引,结果是检索会很慢
extra列返回的描述的意义
Distinct:一旦MYSQL找到了与行相联合匹配的行,就不再搜索了
Not exists: MYSQL优化了LEFT JOIN,一旦它找到了匹配LEFT JOIN标准的行,就不再搜索了
Range checked for each Record(index map:#):没有找到理想的索引,因此对于从前面表中来的每一个行组合,MYSQL检查使用哪个索引,并用它来从表中返回行。这是使用索引的最慢的连接之一
Using filesort: 看到这个的时候,查询就需要优化了。MYSQL需要进行额外的步骤来发现如何对返回的行排序。它根据连接类型以及存储排序键值和匹配条件的全部行的行指针来排序全部行
Using index: 列数据是从仅仅使用了索引中的信息而没有读取实际的行动的表返回的,这发生在对表的全部的请求列都是同一个索引的部分的时候
Using temporary 看到这个的时候,查询需要优化了。这里,MYSQL需要创建一个临时表来存储结果,这通常发生在对不同的列集进行ORDER BY上,而不是GROUP BY上
Where used 使用了WHERE从句来限制哪些行将与下一张表匹配或者是返回给用户。如果不想返回表中的全部行,并且连接类型ALL或index,这就会发生,或者是查询有问题不同连接类型的解释(按照效率高低的顺序排序)
system 表只有一行:system表。这是const连接类型的特殊情况
const:表中的一个记录的最大值能够匹配这个查询(索引可以是主键或惟一索引)。因为只有一行,这个值实际就是常数,因为MYSQL先读这个值然后把它当做常数来对待
eq_ref:在连接中,MYSQL在查询时,从前面的表中,对每一个记录的联合都从表中读取一个记录,它在查询使用了索引为主键或惟一键的全部时使用
ref:这个连接类型只有在查询使用了不是惟一或主键的键或者是这些类型的部分(比如,利用最左边前缀)时发生。对于之前的表的每一个行联合,全部记录都将从表中读出。这个类型严重依赖于根据索引匹配的记录多少—越少越好
range:这个连接类型使用索引返回一个范围中的行,比如使用>或<查找东西时发生的情况 index: 这个连接类型对前面的表中的每一个记录联合进行完全扫描(比ALL更好,因为索引一般小于表数据) ALL:这个连接类型对于前面的每一个记录联合进行完全扫描,这一般比较糟糕,应该尽量避免

freebsd系统一启动就进入单用户模式

提示大概和下面一样

/dev/ad0s1a:File System Clean;Skipping Checks
/dev/ados1a:Clean,480970 free(226 frags,60093 blocks,0.0% fragmentation)
/dev/ados1e:DEFER FOR BACKGROUND CHECKING
/dev/ados1f:DEFER FOR BACKGROUND CHECKING
ad0:FAILURE -READ_DMA status=51 error=40 LBA=3306739
/dev/ad0s1d:CANNOT READ BLK:160948
/dev/ad0s1d:UNEXPECTED SOFT UPDATE INCONSISTENCY;RUN fsck MANUALLY.
THE FOLLOWING FILE SYSTEM HAD AN UNEXPECTED INCONSISTENCY:
usf:/dev/ados1d(/usr)
Automatic file system check failed;help!
OCT 24 19:18:42 init:/bin/sh on /etc/rc terminated
abnormally,going to single use mode
Enter full pathname of shell or RETURN for /bin/sh:

####################

原因:文件系统崩溃了,各分区损坏

解决方法:

# fsck -y

#mount -a

#reboot

即可

##############附################

======================================================
为 FreeBSD UFS2 文件系统恢复受损的主超级块

HOWTO: Recover damaged FreeBSD UFS2 file systems with damaged master super-block

Copyright © Xin LI, 2006.
All Rights Reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

注意:本文介绍的方法部分,假定读者对UFS文件系统,以及FreeBSD的日常操作相当熟悉;请勿轻易执行本文介绍的操作,本文中的操作,可能导致fsck_ffs(无法修正的严重问题。由于在此本人已明确告知读者这一风险,据此,对于由于执行这些操作导致的任何数据损失,本人明示不承担任何责任。

FreeBSD的UFS文件系统的布局结构,在设计时已经考虑到了磁盘立体结构上发生各种损坏的可能性。UFS考虑到了磁盘可能发生单轨、整柱面或整面磁盘的数据发生损毁的可能性。

一般而言,文件系统都需要保存带有“索引”或“描述”性质的关键数据,通常这种数据也称为“元数据”(metadata)。在UFS和UFS2文件系统中,这些元数据包括了超级块(super-block)、柱面组记帐信息等。在UFS2文件系统中的每个超级块大约需要占用3个扇区,它包含了对整个文件系统的描述性信息,因此对于文件系统而言,超级块是非常重要的。在最初的FFS(Berkeley Fast File System,FreeBSD的UFS和UFS2文件系统,事实上是FFS的演化版本)设计中,为了使文件系统在遭到毁灭性打击,如硬盘发生整轨、整面或全柱面损毁时能够得以恢复,在文件系统初始化时,会将超级块复制到整个磁盘的多个位置,以便在发生硬件损坏时能够读取。

有时,由于软件或临时性的硬件信号干扰,也会导致超级块损坏。在文件系统挂载时,系统只会读取主超级块的内容;此处也会保存一些记帐信息。主超级块在文件系统的生命周期内,会不断地发生修改,以反映文件系统目前的状态。为了确保备份的安全,备份超级块不会跟进这些变动。在必要时,fsck_ffs(程序能够根据磁盘上的其它元数据,重新计算应该应用到超级块上的变动。

在FreeBSD中,对于损毁严重的主超级块,系统会给出超级块不正确的提示而拒绝挂载;fsck_ffs(在不指定使用备份超级块时,也会出现类似的问题(我认为这是一个bug,在大约1994年的时候引入,如果有时间的话我会修正这个问题)。要在fsck_ffs的过程中指定使用哪个超级块,可以使用fsck_ffs的-b参数来指定。

我个人建议,只要条件允许,在执行任何数据恢复操作之前,第一步是先将整个磁盘分区进行备份。在FreeBSD中,可以使用dd来完成这项任务。然后,所有的操作,都应在副本而不是正本上运行。如果正本属于硬件损坏,特别是介质损毁,还应从副本复制副本来用于实际操作。

如何确定备份超块的位置

对于UFS2而言,第一个备份超块的位置,通常在该文件系统的第160扇区。如果之前没有记录超块的其它备份的位置,可以用下列命令来得到:

newfs -N /dev/da0s1a

注意,如果在执行newfs时指定了任何其它参数,还需要一起指定。注意,-N是必须的,否则newfs将真的创建文件系统,并使恢复超块不再可能。

这之后,可以尝试使用fsck_ffs(来修复:

fsck_ffs -b 160 -fy /dev/da0s1a

对于损坏非常严重的文件系统,则不应使用前述操作,而应在确认备份超块没有问题的前提下,直接使用它来覆盖。

下面的命令,能够将位于/dev/da0s1a文件系统的备份超块以类似debug的形式dump出来:

dd if=/dev/da0s1a iseek=160 bs=512 count=3 | hexdump -C

一定要仔细确认这个超块没有受到损毁!请参见 sys/ufs/ffs/fs.h 中定义的struct fs结构。

之后,首先备份现有的、占据超块位置的数据。这一步并非严格必须,但很明显,做事后诸葛亮是没有意义的。对于UFS2文件系统,主超级块的起点是文件系统的第128扇区(随配置不同,这个值可能有所差异)。

dd if=/dev/da0s1a iseek=128 bs=512 count=3 of=bad_superblock

接下来,复制备份超块到一个文件:

dd if=/dev/da0s1a iseek=160 bs=512 count=3 of=backup_superblock

最后,用该超块覆盖主超块:

dd if=backup_superblock oseek=128 bs=512 count=3 of=/dev/da0s1a

由于统计信息均不正确,此时磁盘不应投入正常使用。由于我们首先要做的是修复数据,因此此时应以只读方式挂载:

mount -ordonly /dev/da0s1a /mnt/

并从其中复制数据:

cd /mnt
find . -type f [其它条件] | tar cfT – – | tar xf – -C /recovery/

这之后,使用fsck_ffs(对其进行处理,可以进一步得到一些数据:

fsck -fy /dev/da0s1a

几点需要注意的事情:

a) 挂接损坏的文件系统,有可能随时导致内核以极其惨烈的方式崩溃。目前为止,我发现过的此类问题均已进行了修正,但很难说这类问题已经完全不存在。理想状况下,应使用NFS挂接一个远程的文件系统用于写操作;每处理完一个损坏的文件系统,应重启一次。
b) 损坏严重的文件系统会包含一个甚至大量损坏的柱面组信息。这些信息会使文件信息发生损坏。这种情况几乎已经没有可能修正数据了,在复制数据时,应剔除这些数据。
c) 前述操作必须非常小心进行。所有操作均可能导致fsck无法修正的问题。
===============================================================

先备份数据后 准备尝试如上方法 !@!

原文 http://hi.baidu.com/blvm/blog/item/119ca92a5c5bfd29d42af109.html