提供7*24专业Sybase数据库远程及现场技术支持,Sybase ASE及Sybase SQL Anywhere数据库修复服务,
请联系手机:(微信),QQ:289965371!
We supply technical support for Sybase ASE and Sybase SQL Anywhere, also have many years of experience in recovering data from damanged Sybase devices.
Please contact us:
Phone:
Wechat: 13811580958
QQ: 289965371

关于unix下tar解压时的两个错误问题及解决方法

在使用tar命令解压文件的时候,两次都出现了checksum error的错误,只是错误信息稍微有点差别。

情况1:

最近配置AIX下的Java环境,使用tar -xvf时总出现checksum error的信息。

# tar -xvf Java5_64.sdk.tar
x Java5_64.sdk, 74738688 bytes, 145974 media blocks.
tar: 0511-169 A directory checksum error on media; -265812960 not equal to 67621

最后发现原因,是因为我使用ftp下载时没有设置为二进制方式传输数据,ftp会默认使用Ascii的方式来传输,这样会破环文件。

解决方法: 登录ftp之后,输入bin,然后再使用get文件就可以了。

情况2:

用tar命令解压一个非tape archive文件时(格式为tgz),也报类似的错误!

-bash-3.2$ tar -xvf ase1503_aix64_2.tgz
tar: 0511-169 A directory checksum error on media; 0 not equal to 67634.

解决方法:先将tgz压缩文件转化成tar格式的,

$gunzip -S tgz ase1503_aix64_2.tgz

然后再用tar命令解压生成的ase1503_aix64_2.tar文件就可以了。

$tar -xvf ase1503_aix64_2.tar

————————————————————————————————————
——— 本文为andkylee个人原创,请在尊重作者劳动成果的前提下进行转载;
——— 转载务必注明原始出处 : http://www.dbainfo.net
——— 关键字:解压 tar 错误 aix gunzip
————————————————————————————————————

关于SQL2005安装失败的一种可行性解决办法

关于SQL2005安装失败的一种可行性解决办法

1、在安装向导过程中,出现如下错误:

{英文原意如下
When you install Microsoft SQL Server 2005, you receive the following error message:
There was an unexpected failure during the setup wizard. You may review the setup logs and/or click the help button for more information.}

2、当我检查C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Summary.txt

Microsoft SQL Server 2005 9.00.1399.06
==============================
OS Version      : Microsoft Windows Server 2003 family, Enterprise Edition Service Pack 2 (Build 3790)
Time            : Wed Oct 10 08:34:17 2007

CSZ : 执行安装向导期间出错。有关详细信息,您可以查看安装日志和/或单击“帮助”按钮。
SQL Server 安装程序失败。有关详细信息,请查看 %ProgramFiles%\Microsoft SQL Server\90\Setup Bootstrap\LOG\Summary.txt 中的安装日志文件。
Time            : Wed Oct 10 08:42:32 2007
List of log files:
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_Core(Local).log
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_Datastore.xml
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_.NET Framework 2.0.log
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_Core.log
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Summary.txt
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_.NET Framework 2.0 LangPack.log
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_.NET Framework Upgrade Advisor.log
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_.NET Framework Upgrade Advisor LangPack.log
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_.NET Framework Windows Installer.log
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_.NET Framework Windows Installer LangPack.log
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_Support.log
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_SCC.log
C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_WI.log

3、当我检查到日志文件SQLSetup0001_CSZ_Core.log,发现在日志文件末尾有如下的错误提示:
{英文原意如下:
When you examine the SQLSetupNumber_ServerName_Core(local).log file at this point, you notice the following error message at the end of the log file: }

Running: InstallToolsAction.10 at: 2007/9/10 8:35:36
Error: Action "InstallToolsAction.10" threw an exception during execution.  Error information reported during run:
Target collection includes the local machine.
Fatal Exception caught while installing package: "10"
Error Code: 0x80070002 (2)
Windows Error Text: 系统找不到指定的文件。
Source File Name: sqlchaining\sqlprereqpackagemutator.cpp
Compiler Timestamp: Tue Aug  9 01:14:20 2005
Function Name: sqls::SqlPreReqPackageMutator::modifyRequest
Source Line Number: 196
---- Context -----------------------------------------------
sqls::InstallPackageAction::perform
WinException caught while installing package. : 1603
Error Code: 0x80070643 (1603)
Windows Error Text: 安装时发生严重错误
Source File Name: packageengine\installpackageaction.cpp
Compiler Timestamp: Fri Jul  1 01:28:25 2005
Function Name: sqls::InstallPackageAction::perform
Source Line Number: 167
---- Context -----------------------------------------------
sqls::InstallPackageAction::perform
Error: Failed to add file :"C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_.NET Framework 2.0.log" to cab file : "C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\SqlSetup0001.cab" Error Code : 2
Error: Failed to add file :"C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_.NET Framework 2.0 LangPack.log" to cab file : "C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\SqlSetup0001.cab" Error Code : 2
Error: Failed to add file :"C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_.NET Framework Upgrade Advisor.log" to cab file : "C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\SqlSetup0001.cab" Error Code : 2
Error: Failed to add file :"C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_.NET Framework Upgrade Advisor LangPack.log" to cab file : "C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\SqlSetup0001.cab" Error Code : 2
Error: Failed to add file :"C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_.NET Framework Windows Installer.log" to cab file : "C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\SqlSetup0001.cab" Error Code : 2
Error: Failed to add file :"C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\Files\SQLSetup0001_CSZ_.NET Framework Windows Installer LangPack.log" to cab file : "C:\Program Files\Microsoft SQL Server\90\Setup Bootstrap\LOG\SqlSetup0001.cab" Error Code : 2
Running: UploadDrWatsonLogAction at: 2007/9/10 8:42:36
Message pump returning: 1603

4、问题解决
在d:盘根目录下建立sqlserver2005Setup目录,再在d:\sqlserver2005Setup\下创建2个目录
\Servers
\Tools
如 果是2个cd的安装程序,将cd1上的所有文件拷贝到Servers目录下,将cd2目录下的所有文件拷贝到Tools下;如果是一个dvd的安装程序, 则分别把dvd上的Servers和Tools目录下的所有文件分别拷贝到d:\sqlserver2005Setup\Servers和d: \sqlserver2005Setup\Tools
再进行安装应该就没有问题。

还有需要注意的问题
安装到上述错误的时候,SQL Server2005很多文件已经安装到硬盘上了,所有你重新安装的时候,可能会遇到
1、提示磁盘空间不够
目标磁盘中的空间不足,无法执行当前的 SQL Server 安装。若要继续,请释放磁盘空间以安装所选功能、为此次安装选择较少的功能或将所选功能安装到另一个驱动器中。

2、安装仍然失败
所以建议你重新安装之前,到添加删除程序里把SQL Server2005卸载掉!

上述问题是8月份我在一个客户那里搞了一个晚上都没有搞定,最后第二天早上在宾馆里上网到微软的网站上找到了答案,真是让人哭笑不得。

微软网站上标题为:
Error message when you install SQL Server 2005: "There was an unexpected failure during the setup wizard"

Article ID : 916760
Last Review : May 9, 2006
Revision : 2.0
Bug #: 408784 (SQLBUDT)

------------------------------------------------------------------------

性能计数器问题,主要是卸载SQL2005,再重新安装的错误,这方面的文章在网上很多,网友可搜索一下。

关于sql.cab找不到网上这样的疑问同样很多,但是没有明确的解答。这里说说我是怎么解决的。

应该说这是MSDN  SQL2005安装程序的一个bug(不是十分确定),在SQL2005安装目录有两个子目录,一个是Servers目录, 一个是Tools目录,当初我只把Servers拷贝我硬盘上了,所以安装时如果选择了工作站组件、联机丛书和开发工具选项,安装到最后,就会提示你 sql.cab找不到(或其它安装不成功的提示信息),其实安装sql时先不要选该项,这样就可以安装成功,最后安装Tools的setup程序,它就是 仅安装该选项的安装包。

-------------------------------------------------------------

假如,以上这种办法还是出现SQL.CAB找不到类似的错误,那么你可以在上面那个办法的基础上,打开TOOLS文件夹(有的可能是CD2文件夹),找到TOOLS\Setup\SqlRun_Tools.msi并,运行,然后按照正常的安装流程来,这样应该可以解决了

题外话:我熬了好几个夜,把SQL2005装了删,删了装,终于装上了,真是辛苦啊~~。以上是我的安装解决办法的总结,有的是网站感觉比较有用的转载,有的是自己实际经历的过程的记录~~!

转自:http://www.nmju.net/article.asp?id=101

unix下以十六进制形式修改文件内容[转]

本帖子转自:echoaix  嘟嘟之家  http://blog.chinaunix.net/u/10212/showart.php?id=88834

向echoaix这位sybase 高手表示感谢。

转下面内容的原因是作个记录方便以后来查看如何在unix下修改文件内容。在windows下以十六进制形式修改文件内的数据很简单,可以用 UltraEdit这个强大的工具。我编出来了工具能够实现从sybase数据设备文件中提取数据和翻译解析sybase日志设备文件内容,这个过程中 UE提供了莫大的帮助。我编写的工具跟UE没有关系。但是,有时候对sybase设备文件做一个小小的修改的时候,我喜欢用UE。

但是,在unix环境下的sybase设备文件该如何修改呢?总不能每次都ftp下载下来,用windows下的Ultraedit改好了后再上传到服务器吧?

这样做太麻烦。

幸运的是,早有高手提供了解决的方法!厉害!

下面的代码我没有完全看明白,主要是没unix写过shell的原因。

大概意思是:向sh脚本传递3个参数,用指定数据替换指定文件中的相应偏移的相同字节的数据。

用od定位出被修改的偏移位置。然后用echo向dd传入数据,dd会处理这个文件。

至于如何将4个字节的数据转化到数组里的那段代码,我没有看懂!

-----------------------------------------------------------------------------

环境 :
iBM aix 4.3和sybase11.9 只有这环境
master设备/dev/rlvsybmaster1
前面还是一样看page内容:
dbcc traceon(3604)
go
dbcc tablealloc(sysdatabases) 或  select first from sysindexes where id = object_id("sysdatabases" and indid=1\\找到数据页(pageno)
go
dbcc page(master,pageno,1,0)  \\从硬盘上拿这页内容详细内容
go
master的如下:
Offset 266 -
3256210a:  02000001 00010000 80000001 000008a1  ................
3256211a:  00000000 00000000 00009179 00fe9caa  ...........y....
3256212a:  0005adda 00120034 6d617374 65728000  .......4master..
3256213a:  03302e28                             .0.(.
用od命令定位(od命令真好)
#od -H /dev/lvsybmaster1 |grep '02000001 00010000 80000001 000008a1'
报找不到,郁闷,仔细看看dbcc page用法1(print page header, data rows and row offset table), 2 (print page header and hex dump of data page) 。改用dbcc page(master,pageno,2,0)
结果如下:不只这些,择了master的
32562110:  00008000 00010000 08a10000 00000000  ................
32562120:  00000000 917900fe 9caa0005 adda0012  .....y..........
32562130:  00346d61 73746572 80000330 2e280201  .4master...0.(..
32562140:  00030001 00000000 00010000 01e00000  ................
32562150:  00000000 00000000 917900fd acd60013  .........y......
32562160:  9866001e 00336d6f 64656c80 00032f2d  .f...3model.../-
原来是顺序不同,怪不得grep找不到,再来:
#od -H /dev/lvsybmaster1 |grep '00008000 00010000 08a10000 00000000' 结果
2024420  00008000 00010000 08a10000 00000000  找到,注意这的偏移量是8进制的
找到如何改呢?知道应该用dd,叨咕半天,不行,上网求助 ,在liveunix看到“炸鸡”高人的帖子,给了一个sh如下
cat chvgid.sh
#!/usr/bin/ksh
vgid=$1
disk=$2

set -A a `echo $vgid|\
awk '{
for (f=1; f <= length($0); f=f+2) {
print "ibase=16\nobase=8\n"toupper(substr($0,f,2))
}
}'|bc 2>/dev/null`
/usr/bin/echo "\0"${a[0]}"\0"${a[1]}"\0"${a[2]}"\0"${a[3]}"\c"|dd bs=1 seek=3600 of=/dev/$disk
原来他是要直接改VGID的信息,看来能改的东西真不少。
到现在这个sh我还是看不很明白,但是用没问题,自己改了改如下
#cat chfile.sh
#!/usr/bin/ksh
dstatus=$1
filename=$2
offset=$3

set -A a `echo $dstatus|\
awk '{
for (f=1; f <= length($0); f=f+2) {
print "ibase=16\nobase=8\n"toupper(substr($0,f,2))
}
}'|bc 2>/dev/null`

/usr/bin/echo "\0"${a[0]}"\0"${a[1]}"\0"${a[2]}"\0"${a[3]}"\c"|dd bs=1 seek=$offset of=$filename conv=notrunc
加了$3偏移量,还有注意加conv=notrunc,要不你在修改点后的数据可能都没了。这的偏移量可是10进制的
od -Ad -H /dev/lvsybmaster1 |grep '00008000 00010000 08a10000 00000000' 找10进制偏移
0534800  00008000 00010000 08a10000 00000000
停sybase
改master设备
#chfile.sh 00000000 /dev/lvsybmaster1 534800
4+0 records in.
4+0 records out.
就是要把00008000 改为00000000
重启sybase ok

解决xmanager不能远程登录Solaris10主机的问题[转]

默认情况下.Solaris10安装完成后.启动是cde界面.但有时用xmanager登录时始终无法建立连接. 如果确认不是防火墙等方面的原因.可以用如下方法解决

1. 关闭默认的cde服务
svcadm disable cde-login
用ps-ef|grep dtlogin 应该看不到dtlogin进程了
2. 进入/etc/X11/gdm
编辑gdm.conf文件(也有可能是这两个文件/usr/share/gdm/defaults.conf 和/etc/X11/gdm/custom.conf) 编辑如下2个地方

找到[xdmcp]字段.
将Enable=flase改为true
将Port=177前的注释取消

然后保存退出.
3.ps-ef|grep gdm 确认目前没有gdm进程.如有杀之
4. 启动gdm服务

# svcs -a|grep gdm
disabled       Aug_07   svc:/application/gdm2-login:default
# svcadm enable svc:/application/gdm2-login:default

然后在xmanager里建立新连接. 这样就可以登录了. 在登录界面的左上角选择cde.熟悉的cde界面就又回来了.

转载2 from: 彬彬有理 http://blog.chinaunix.net/u2/77786/showart_1161371.html -------------------------------------------------------------------------------------------------------------------------------------

前几天有台v890的服务器,没有显卡,装完solaris10系统(完整分发安装)后不能使用xmanager进连接,被折腾了两天,之前上网查说安装时使用 带oem的完整分发安装, 不过没有试,现把可能管用的方法写出来希望对大家有帮助。
1、solaris 10默认开启dxmcp,xmanger采用默认设置即可。对于这种情况应首先检查pc机到服务器间的网络是否有问题,防火墙是否开启了177端口,如可以的话关掉防火墙,仍不能解决问题可使pc 机与服务器直接相连试试。
2、设置系统自动进入桌面
#/usr/dt/bin/dtconfig -e  启用自动进入桌面
/usr/dt/bin/dtconfig -reset 配置资源重新装载
3.检查dtlogin进程是否启动,并且使用177端口
# ps -ef | grep dtlogin
root 101252 100995 0 15:09:01 ? 0:00 /usr/dt/bin/dtlogin -daemon -udpPort 0
root 101420 101419 0 15:13:47 pts/3 0:00 grep dtlogin
这里系统缺省监听udp port 0,就无法监听到177端口上的XDMCP请求
解决方案如下:
# /etc/init.d/dtlogin stop 关掉服务
# /usr/dt/bin/dtlogin -daemon &   换daemon启动方式或者显式指定dtlogin -udpPort 177
修改服务参数使其启动时自动使用177端口:
# svccfg
svc:> select application/graphical-login/cde-login
svc:/application/graphical-login/cde-login> setprop dtlogin/args = "\ -udpPort 177"
svc:/application/graphical-login/cde-login> quit
之后重起dtlogin 服务  # svcadm restart cde-login

大端(Big Endian)与小端(Little Endian)详解[转]

大端(Big Endian)与小端(Little Endian)简介
///////////////////////////////////////////////////////
1. 你从哪里来?
端模式(Endian)的这个词出自Jonathan Swift书写的《格列佛游记》。这本书根据将鸡蛋敲开的方法不同将所有的人分为两类,从圆头开始将鸡蛋敲开的人被归为Big Endian,从尖头开始将鸡蛋敲开的人被归为Littile Endian。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开。在计算机业Big Endian和Little Endian也几乎引起一场战争。在计算机业界,Endian表示数据在存储器中的存放顺序。采用大端方式 进行数据存放符合人类的正常思维,而采用小端方式进行数据存放利于计算机处理。下文举例说明在计算机中大小端模式的区别。
//////////////////////////////////////////////////////
2. 读书百遍其义自见
小端口诀: 高高低低 -> 高字节在高地址, 低字节在低地址
大端口诀: 高低低高 -> 高字节在低地址, 低字节在高地址

long test = 0x313233334;
小端机器:
低地址 --> 高地址
00000010: 34 33 32 31         -> 4321

大端机器:
低地址 --> 高地址
00000010: 31 32 33 34         -> 4321
test变量存储的是的0x10这个地址,
那编译器怎么知道是读四个字节呢? -> 根据变量test的类型long可知这个变量占据4个字节.
那编译器怎么读出这个变量test所代表的值呢? -> 这就根据是little endian还是big endian来读取
所以, 小端, 其值为0x31323334; 大端, 其值为0x34333231

htonl(test) 的情况:     ->其值为: 0x34333231
小端机器:
00000010: 31 32 33 34         -> 1234
大端机器:
00000010: 34 33 32 31         -> 4321
/////////////////////////////////////////////////////////////////////////////////////
3. 拿来主义
Byte Endian是指字节在内存中的组织,所以也称它为Byte Ordering,或Byte Order。
对于数据中跨越多个字节的对象, 我们必须为它建立这样的约定:
(1) 它的地址是多少?
(2) 它的字节在内存中是如何组织的?
针对第一个问题,有这样的解释:
对于跨越多个字节的对象,一般它所占的字节都是连续的,它的地址等于它所占字节最低地址。(链表可能是个例外, 但链表的地址可看作链表头的地址)。
比如: int x, 它的地址为0x100。 那么它占据了内存中的Ox100, 0x101, 0x102, 0x103这四个字节(32位系统,所以int占用4个字节)。
上面只是内存字节组织的一种情况: 多字节对象在内存中的组织有一般有两种约定。 考虑一个W位的整数。
它的各位表达如下:[Xw-1, Xw-2, ... , X1, X0],它的
MSB (Most Significant Byte, 最高有效字节)为 [Xw-1, Xw-2, ... Xw-8];
LSB (Least Significant Byte, 最低有效字节)为 [X7,X6,..., X0]。
其余的字节位于MSB, LSB之间。

LSB和MSB谁位于内存的最低地址, 即谁代表该对象的地址?
这就引出了大端(Big Endian)与小端(Little Endian)的问题。
如果LSB在MSB前面, 既LSB是低地址, 则该机器是小端; 反之则是大端。
DEC (Digital Equipment Corporation,现在是Compaq公司的一部分)和Intel的机器(X86平台)一般采用小端。
IBM, Motorola(Power PC), Sun的机器一般采用大端。
当然,这不代表所有情况。有的CPU即能工作于小端, 又能工作于大端, 比如ARM, Alpha,摩托罗拉的PowerPC。 具体情形参考处理器手册。
具体这类CPU是大端还是小端,应该和具体设置有关。
(如,Power PC支持little-endian字节序,但在默认配置时是big-endian字节序)
一般来说,大部分用户的操作系统(如windows, FreeBsd,Linux)是Little Endian的。少部分,如MAC OS ,是Big Endian 的。
所以说,Little Endian还是Big Endian与操作系统和芯片类型都有关系。因此在一个处理器系统中,有可能存在大端和小端模式同时存在的现象。这一现象为系统的软硬件设计带来了不小的 麻烦,这要求系统设计工程师,必须深入理解大端和小端模式的差别。大端与小端模式的差别体现在一个处理器的寄存器,指令集,系统总线等各个层次中。

Linux系统中,你可以在/usr/include/中(包括子目录)查找字符串BYTE_ORDER(或
_BYTE_ORDER, __BYTE_ORDER),确定其值。BYTE_ORDER中文称为字节序。这个值一般在endian.h或machine/endian.h文件中可以找到,有时在feature.h中,不同的操作系统可能有所不同。

【用函数判断系统是Big Endian还是Little Endian】
enum {FALSE = 0, TRUE = !FALSE};
typedef short BOOL;
BOOL IsBig_Endian()
//如果字节序为big-endian,返回true;
//反之为   little-endian,返回false
{
unsigned short test = 0x1122;
if(*( (unsigned char*) &test ) == 0x11)
return TRUE;
else
return FALSE;

}//IsBig_Endian()

//////////////////////////////////////////////////////////////////////////////

可以做个实验
在windows上下如下程序
#include <stdio.h>
#include <assert.h>

void main( void )
{
short test;
FILE* fp;

test = 0x3132; //(31ASIIC码的’1’,32ASIIC码的’2’)
if ((fp = fopen ("c:\\test.txt", "wb")) == NULL)
assert(0);
fwrite(&test, sizeof(short), 1, fp);
fclose(fp);
}
然后在C盘下打开test.txt文件,可以看见内容是21,而test等于0x3132,可以明显的看出来x86的字节顺序是低位在前.如果我们把这段 同样的代码放到(big-endian)的机器上执行,那么打出来的文件就是12.这在本机中使用是没有问题的.但当你把这个文件从一个big- endian机器复制到一个little-endian机器上时就出现问题了.
如上述例子,我们在big-endian的机器上创建了这个test文件,把其复制到little-endian的机器上再用fread读到一个 short里面,我们得到的就不再是0x3132而是0x3231了,这样读到的数据就是错误的,所以在两个字节顺序不一样的机器上传输数据时需要特别小 心字节顺序,理解了字节顺序在可以帮助我们写出移植行更高的代码.
正因为有字节顺序的差别,所以在网络传输的时候定义了所有字节顺序相关的数据都使用big-endian,BSD的代码中定义了四个宏来处理:
#define ntohs(n)     //网络字节顺序到主机字节顺序 n代表net, h代表host, s代表short
#define htons(n)     //主机字节顺序到网络字节顺序 n代表net, h代表host, s代表short
#define ntohl(n)      //网络字节顺序到主机字节顺序 n代表net, h代表host, s代表 long
#define htonl(n)      //主机字节顺序到网络字节顺序 n代表net, h代表host, s代表 long

举例说明下这其中一个宏的实现:
#define sw16(x) \
((short)( \
(((short)(x) & (short)0x00ffU) << 8) | \
(((short)(x) & (short)0xff00U) >> 8) ))
这里实现的是一个交换两个字节顺序.其他几个宏类似.

我们改写一下上面的程序
#include <stdio.h>
#include <assert.h>

#define sw16(x) \
((short)( \
(((short)(x) & (short)0x00ffU) << 8) | \
(((short)(x) & (short)0xff00U) >> 8) ))

#define sw32(x) \
((long)( \
(((long)(x) & (long)0x000000ff) << 24) | \
(((long)(x) & (long)0x0000ff00) << 8) | \
(((long)(x) & (long)0x00ff0000) >> 8) | \
(((long)(x) & (long)0xff000000) >> 24) ))

// 因为x86下面是低位在前,需要交换一下变成网络字节顺序
#define htons(x) sw16(x)
#define htonl(x) sw32(x)

void main( void )
{
short test;
FILE* fp;

test = htons(0x3132); //(31ASIIC码的’1’,32ASIIC码的’2’)
if ((fp = fopen ("c:\\test.txt", "wb")) == NULL)
assert(0);
fwrite(&test, sizeof(short), 1, fp);
fclose(fp);
}

如果在高字节在前的机器上,由于与网络字节顺序一致,所以我们什么都不干就可以了,只需要把#define htons(x) sw16(x)宏替换为 #define htons(x) (x).
一开始我在理解这个问题时,总在想为什么其他数据不用交换字节顺序?比如说我们write一块buffer到文件,最后终于想明白了,因为都是unsigned char类型一个字节一个字节的写进去,这个顺序是固定的,不存在字节顺序的问题

【大端(Big Endian)与小端(Little Endian)简介】
Byte Endian是指字节在内存中的组织,所以也称它为Byte Ordering,或Byte Order。
对于数据中跨越多个字节的对象, 我们必须为它建立这样的约定:
(1) 它的地址是多少?
(2) 它的字节在内存中是如何组织的?
针对第一个问题,有这样的解释:
对于跨越多个字节的对象,一般它所占的字节都是连续的,它的地址等于它所占字节最低地址。(链表可能是个例外, 但链表的地址可看作链表头的地址)。
比如: int x, 它的地址为0x100。 那么它占据了内存中的Ox100, 0x101, 0x102, 0x103这四个字节(32位系统,所以int占用4个字节)。
上面只是内存字节组织的一种情况: 多字节对象在内存中的组织有一般有两种约定。 考虑一个W位的整数。
它的各位表达如下:[Xw-1, Xw-2, ... , X1, X0],它的
MSB (Most Significant Byte, 最高有效字节)为 [Xw-1, Xw-2, ... Xw-8];
LSB (Least Significant Byte, 最低有效字节)为 [X7,X6,..., X0]。
其余的字节位于MSB, LSB之间。

LSB和MSB谁位于内存的最低地址, 即谁代表该对象的地址?
这就引出了大端(Big Endian)与小端(Little Endian)的问题。
如果LSB在MSB前面, 既LSB是低地址, 则该机器是小端; 反之则是大端。
DEC (Digital Equipment Corporation,现在是Compaq公司的一部分)和Intel的机器(X86平台)一般采用小端。
IBM, Motorola(Power PC), Sun的机器一般采用大端。
当然,这不代表所有情况。有的CPU即能工作于小端, 又能工作于大端, 比如ARM, Alpha,摩托罗拉的PowerPC。 具体情形参考处理器手册。

具体这类CPU是大端还是小端,应该和具体设置有关。
(如,Power PC支持little-endian字节序,但在默认配置时是big-endian字节序)
一般来说,大部分用户的操作系统(如windows, FreeBsd,Linux)是Little Endian的。少部分,如MAC OS ,是Big Endian 的。
所以说,Little Endian还是Big Endian与操作系统和芯片类型都有关系。

Linux系统中,你可以在/usr/include/中(包括子目录)查找字符串BYTE_ORDER(或
_BYTE_ORDER, __BYTE_ORDER),确定其值。BYTE_ORDER中文称为字节序。这个值一般在endian.h或machine/endian.h文件中可以找到,有时在feature.h中,不同的操作系统可能有所不同。

big endian是指低地址存放最高有效字节(MSB),而little endian则是低地址存放最低有效字节(LSB)。
用文字说明可能比较抽象,下面用图像加以说明。比如数字0x12345678在两种不同字节序CPU中的存储顺序如下所示:

Big Endian

低地址                                            高地址
----------------------------------------->
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     12     |      34    |     56      |     78    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Little Endian

低地址                                            高地址
----------------------------------------->
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     78     |      56    |     34      |     12    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

从上面两图可以看出,采用big endian方式存储数据是符合我们人类的思维习惯的.
为什么要注意字节序的问题呢?你可能这么问。当然,如果你写的程序只在单机环境下面运行,并且不和别人的程序打交道,那么你完全可以忽略字节序的存在。但 是,如果你的程序要跟别人的程序产生交互呢?在这里我想说说两种语言。C/C++语言编写的程序里数据存储顺序是跟编译平台所在的CPU相关的,而 J***A编写的程序则唯一采用big endian方式来存储数据。试想,如果你用C/C++语言在x86平台下编写的程序跟别人的J***A程序互通时会产生什么结果?就拿上面的 0x12345678来说,你的程序传递给别人的一个数据,将指向0x12345678的指针传给了J***A程序,由于J***A采取big endian方式存储数据,很自然的它会将你的数据翻译为0x78563412。什么?竟然变成另外一个数字了?是的,就是这种后果。因此,在你的C程序 传给J***A程序之前有必要进行字节序的转换工作。
无独有偶,所有网络协议也都是采用big endian的方式来传输数据的。所以有时我们也会把big endian方式称之为网络字节序。当两台采用不同字节序的主机通信时,在发送数据之前都必须经过字节序的转换成为网络字节序后再进行传输。ANSI C中提供了下面四个转换字节序的宏。
·BE和LE一文的补完
我在8月9号的《Big Endian和Little Endian》一文中谈了字节序的问题,原文见上面的超级链接。可是有朋友仍然会问,CPU存储一个字节的数据时其字节内的8个比特之间的顺序是否也有 big endian和little endian之分?或者说是否有比特序的不同?
实际上,这个比特序是同样存在的。下面以数字0xB4(10110100)用图加以说明。

Big Endian

msb                                                         lsb
---------------------------------------------->
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|   1  |   0  |   1  |   1  |   0  |   1  |   0  |   0  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Little Endian

lsb                                                         msb
---------------------------------------------->
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|   0  |   0  |   1  |   0  |   1  |   1  |   0  |   1  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

实际上,由于CPU存储数据操作的最小单位是一个字节,其内部的比特序是什么样对我们的程序来说是一个黑盒子。也就是说,你给我一个指向0xB4这个数的 指针,对于big endian方式的CPU来说,它是从左往右依次读取这个数的8个比特;而对于little endian方式的CPU来说,则正好相反,是从右往左依次读取这个数的8个比特。而我们的程序通过这个指针访问后得到的数就是0xB4,字节内部的比特 序对于程序来说是不可见的,其实这点对于单机上的字节序来说也是一样的。
那可能有人又会问,如果是网络传输呢?会不会出问题?是不是也要通过什么函数转换一下比特序?嗯,这个问题提得很好。假设little endian方式的CPU要传给big endian方式CPU一个字节的话,其本身在传输之前会在本地就读出这个8比特的数,然后再按照网络字节序的顺序来传输这8个比特,这样的话到了接收端 不会出现任何问题。而假如要传输一个32比特的数的话,由于这个数在littel endian方存储时占了4个字节,而网络传输是以字节为单位进行的,little endian方的CPU读出第一个字节后发送,实际上这个字节是原数的LSB,到了接收方反倒成了MSB从而发生混乱。

【用函数判断系统是Big Endian还是Little Endian】
bool IsBig_Endian()
//如果字节序为big-endian,返回true;
//反之为   little-endian,返回false
{
unsigned short test = 0x1122;
if(*( (unsigned char*) &test ) == 0x11)
return TRUE;
else
return FALSE;

}//IsBig_Endian()



=======================================================================

字节序问题--大端法小端法

一、字节序定义

字节序,顾名思义字节的顺序,再多说两句就是大于一个字节类型的数据在内存中的存放顺序(一个字节的数据当然就无需谈顺序的问题了)。

其实大部分人在实际的开发中都很少会直接和字节序打交道。唯有在跨平台以及网络程序中字节序才是一个应该被考虑的问题。

在所有的介绍字节序的文章中都会提到字节序分为两类:Big-Endian和Little-Endian。引用标准的Big-Endian和Little-Endian的定义如下:
a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
c) 网络字节序:4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。由于 TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。比如,以太网头部中2字节的“以太网帧类型”,表示后面数据的类型。对于ARP请求或应答的以太网帧类型 来说,在网络传输时,发送的顺序是0x08,0x06。在内存中的映象如下图所示:
栈底 (高地址)
---------------
0x06 -- 低位
0x08 -- 高位
---------------
栈顶 (低地址)
该字段的值为0x0806。按照大端方式存放在内存中。

二、高/低地址与高低字节

首先我们要知道我们C程序映像中内存的空间布局情况:在《C专家编程》中或者《Unix环境高级编程》中有关于内存空间布局情况的说明,大致如下图:
----------------------- 最高内存地址 0xffffffff
| 栈底
.
.              栈
.
栈顶
-----------------------
|
|
\|/

NULL (空洞)

/|\
|
|
-----------------------

-----------------------
未初始化的数据
----------------(统称数据段)
初始化的数据
-----------------------
正文段(代码段)
----------------------- 最低内存地址 0x00000000

以上图为例如果我们在栈上分配一个unsigned char buf[4],那么这个数组变量在栈上是如何布局的呢[注1]?看下图:
栈底 (高地址)
----------
buf[3]
buf[2]
buf[1]
buf[0]
----------
栈顶 (低地址)

现在我们弄清了高低地址,接着来弄清高/低字节,如果我们有一个32位无符号整型0x12345678(呵呵,恰好是把上面的那4个字节buf看成 一个整型),那么高位是什么,低位又是什么呢?其实很简单。在十进制中我们都说靠左边的是高位,靠右边的是低位,在其他进制也是如此。就拿 0x12345678来说,从高位到低位的字节依次是0x12、0x34、0x56和0x78。

高低地址和高低字节都弄清了。我们再来回顾一下Big-Endian和Little-Endian的定义,并用图示说明两种字节序:
以unsigned int value = 0x12345678为例,分别看看在两种字节序下其存储情况,我们可以用unsigned char buf[4]来表示value:
Big-Endian: 低地址存放高位,如下图:
栈底 (高地址)
---------------
buf[3] (0x78) -- 低位
buf[2] (0x56)
buf[1] (0x34)
buf[0] (0x12) -- 高位
---------------
栈顶 (低地址)

Little-Endian: 低地址存放低位,如下图:
栈底 (高地址)
---------------
buf[3] (0x12) -- 高位
buf[2] (0x34)
buf[1] (0x56)
buf[0] (0x78) -- 低位
---------------
栈顶 (低地址)

在现有的平台上Intel的X86采用的是Little-Endian,而像Sun的SPARC采用的就是Big-Endian。

三、例子

嵌入式系统开发者应该对Little-endian和Big-endian模式非常了解。采用Little-endian模式的CPU对操作数的存放方式是从低字节到高字节,而Big-endian模式对操作数的存放方式是从高字节到低字节。

例如,16bit宽的数0x1234在Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:

内存地址  存放内容
0x4001    0x12
0x4000    0x34

而在Big-endian模式CPU内存中的存放方式则为:

内存地址  存放内容
0x4001    0x34
0x4000    0x12

32bit宽的数0x12345678在Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:

内存地址  存放内容
0x4003     0x12
0x4002     0x34
0x4001     0x56
0x4000     0x78

而在Big-endian模式CPU内存中的存放方式则为:

内存地址  存放内容
0x4003     0x78
0x4002     0x56
0x4001     0x34
0x4000     0x12

三、例子
测试平台 : Sun SPARC Solaris 9 和 Intel X86 Solaris 9
我们的例子是这样的:在使用不同字节序的平台上使用相同的程序读取同一个二进制文件的内容。
生成二进制文件的程序如下 :
/* gen_binary.c */
int main() {
FILE    *fp = NULL;
int     value = 0x12345678;
int     rv = 0;

fp = fopen("temp.dat", "wb");
if (fp == NULL) {
printf("fopen error\n");
return -1;
}

rv = fwrite(&value, sizeof(value), 1, fp);
if (rv != 1) {
printf("fwrite error\n");
return -1;
}

fclose(fp);
return 0;
}

读取二进制文件的程序如下:
int main() {
int             value   = 0;
FILE         *fp     = NULL;
int             rv      = 0;
unsigned        char buf[4];

fp = fopen("temp.dat", "rb");
if (fp == NULL) {
printf("fopen error\n");
return -1;
}

rv = fread(buf, sizeof(unsigned char), 4, fp);
if (rv != 4) {
printf("fread error\n");
return -1;
}

memcpy(&value, buf, 4); // or value = *((int*)buf);
printf("the value is %x\n", value);

fclose(fp);
return 0;
}

测试过程:
(1) 在 SPARC 平台下生成 temp.dat 文件
在 SPARC 平台下读取 temp.dat 文件的结果:
the value is 12345678

在 X86 平台下读取 temp.dat 文件的结果:
the value is 78563412

(1) 在 X86 平台下生成 temp.dat 文件
在 SPARC 平台下读取 temp.dat 文件的结果:
the value is 78563412

在 X86 平台下读取 temp.dat 文件的结果:
the value is 12345678

[ 注 1]
buf[4] 在栈的布局我也是通过例子程序得到的:
int main() {
unsigned char buf[4];

printf("the buf[0] addr is %x\n", buf);
printf("the buf[1] addr is %x\n", &buf[1]);

return 0;
}
output:
SPARC 平台:
the buf[0] addr is ffbff788
the buf[1] addr is ffbff789
X86 平台:
the buf[0] addr is 8047ae4
the buf[1] addr is 8047ae5

两个平台都是 buf[x] 所在地址高于 buf[y] (x > y) 。

如何判断系统是Big Endian还是Little Endian?

在/usr /include/中(包括子目录)查找字符串BYTE_ORDER(或_BYTE_ORDER, __BYTE_ORDER),确定其值。这个值一般在endian.h或machine/endian.h文件中可以找到,有时在feature.h中, 不同的操作系统可能有所不同。一般来说,Little Endian系统BYTE_ORDER(或_BYTE_ORDER,__BYTE_ORDER)为1234,Big Endian系统为4321。大部分用户的操作系统(如windows, FreeBsd,Linux)是Little Endian的。少部分,如MAC OS ,是Big Endian 的。本质上说,Little Endian还是Big Endian与操作系统和芯片类型都有关系。

Processor OS Order
x86 (Intel, AMD, … ) All little-endian
DEC Alpha All little-endian
HP-PA NT little-endian
HP-PA UNIX big-endian
SUN SPARC All? big-endian
MIPS NT little-endian
MIPS UNIX big-endian
PowerPC NT little-endian
PowerPC non-NT big-endian
RS/6000 UNIX big-endian
Motorola m68k All big-endian

Sybase索引物理存储结构分析

迄今已分析出来了sybase中索引(indid>1)的物理存储结构。

索引结构是B-Tree类型的。最顶部叫做根(root),最底层称为叶子(leaf)。一个表可能建有好几个非聚簇索引,这时indid依次为2,3,。。。递增。

对于一个索引,比如indid=2的那个。索引树状结构是分层次的,在sybase数据存储中用level表示,根部级别最高,叶子的级别最低。叶 子(leaf)的级别level为0,往上索引层level为1,再往上位2,。。。最后到达顶部root级别为(N-1,N为所有的层次数)。

不管APL还是DOL表,索引的每层(level)上的页面都是前后链接起来的,这一点有点像APL表中的数据页面上的前、后页链(data page link)。

以下简要演示分析索引结构的过程。

1.

设定成在终端显示dbcc结果信息。

1
2
dbcc traceon(3604)
go

2.

查看syspartitions表的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
1> select *from sysobjects
2> where name='PartitionTestTable'
3> go
name
id          uid         type userstat sysstat indexdel schemacnt
sysstat2    crdate                     expdate
deltrig     instrig     updtrig     seltrig     ckfirst     cache
audflags    objspare    versionts
loginame
identburnmax                              spacestate
erlchgts
-----------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------
----------------------------------------------------------------------------
----------- ----------- ---- -------- ------- -------- ---------
----------- -------------------------- --------------------------
----------- ----------- ----------- ----------- ----------- ------
----------- ----------- --------------------------
------------------------------
----------------------------------------- ----------
------------------
PartitionTestTable
1223672376           1 U           0      99        2         0
73728        Feb 24 2010  4:43PM        Feb 24 2010  4:43PM
0           0           0           0           0      0
0           0 NULL
NULL
NULL       NULL
NULL
(1 row affected)
1> select * from syspartitions
2> where id =  1223672376
3> go
name
indid  id          partitionid segment status      datoampage
indoampage  firstpage   rootpage    data_partitionid
crdate
cdataptnname
-----------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------
----------------------------------------------------------------------------
------ ----------- ----------- ------- ----------- -----------
----------- ----------- ----------- ----------------
--------------------------
---------------------------------------------------------------------------------
------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------
zhang
0  1223672376  1239672433       1           0       61288
0       61289       61997                0
Feb 24 2010  4:43PM
NULL
liu
0  1223672376  1255672490       1           0       61296
0       61297       62124                0
Feb 24 2010  4:43PM
NULL
wang
0  1223672376  1271672547       1           0       61304
0       61305       62260                0
Feb 24 2010  4:43PM
NULL
li
0  1223672376  1287672604       1           0       61312
0       61313       62385                0
Feb 24 2010  4:43PM
NULL
idx_PartitionTestTable_id_1431673117
2  1223672376  1431673117       1           2           0
62465       67256       66248                0
Mar  1 2010 11:19AM
NULL
idx_PartitionTestTable_name_1479673288
3  1223672376  1479673288       1           2           0
62721       69816       69386                0
Mar  1 2010  6:03PM
NULL
(6 rows affected)
1>

表PartitionTestTable是在其上的id列建了4个分区的分区表,它有2个索引。idx_PartitionTestTable_id对id列索引,idx_PartitionTestTable_name对name列索引。

我们就分析idx_PartitionTestTable_id这个索引吧。通过syspartitions表我们可以得到四个比较有用的 datoampage,indoampage, firstpage ,rootpage。分别表示数据对象分配页的页号,索引对象分配页的页号,

索引叶子层上的第一页,索引根部的页号。(堆表信息中的firstpage,rootpage意思有些不同。分别表示:数据页的第一个、最后一页。)

有:datoampage=0,indoampage=62465,firstpage= 67256,rootpage=66248.

对于indoampage索引对象分配页,可以这么查看。(PartitionTestTable的objid为: 1223672376)

dbcc listoam(4,1223672376,2)

3.可以看出索引idx_PartitionTestTable_id在14个对象分配页allocation page上的分配情况如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
1> dbcc listoam(4,1223672376,2)
2> go
-----------------------------------------------------------------------------
Partition id: 1431673117     indid:   2 prevpg: 62465 nextpg: 62465
OAM pg cnt:      1      Entry cnt:         14
Row count information is not maintained for index pages.
Used pgs:      716      Unused pgs:        11
Attribute entries:       10
OAM status bits set:  (0x8000 (PG_OAMPG), 0x0008 (PG_OAMATTRIB), 0x0004
(PG_OAMSORT))
LAST SCANNED OAM PAGE:          0
ALLOCATION HINTS     :
62465          0          0          0
0          0          0          0
0          0          0          0
0          0          0
OAM pg #  1:      62465 has the following 14 entries (allocpg:used/unused):
[   0]      62464:  9/  6       63232: 24/  0       63744:  8/  0       64256:
0/  0
[   4]      64768: 16/  0       65024: 16/  0       65280: 16/  0       65536:
16/  0
[   8]      65792:152/  0       66048:240/  0       66304: 96/  0       66560:
48/  0
[  12]      66816: 40/  0       67072: 35/  5
There are 1 entries with zero used/unused values.
---- End of OAM chain for partition 1431673117 ----
DBCC execution completed. If DBCC printed error messages, contact a user with
System Administrator (SA) role.

(此处,暂时不解释以上结果中的情况!)

4.现在回到索引上,先从根部分析。rootpage=66248。

查看66248页上的16进制数据。

dbcc page(4,66248,1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
1> dbcc page(4,66248,1)
2> go
Page not found in Cache: default data cache.
Page read from disk.
BUFFER:
Buffer header for buffer 0x28288000 (Mass head)
page=0x28287000 bdnew=0x00000000 bdold=0x00000000 bhash=0x00000000
bmass_next=0x00000000 bmass_prev=0x00000000 bdbid=4
bvirtpg= [ 0x28288070 vpgdevno=5 vpvpn=132496 vdisk=0x21DE1CB4 ]
bmass_head=0x28288000 bmass_tail=0x28288000
bcache_desc=0x2828B3F0 (cache name='default data cache')
bpool_desc=0x00000000 bdbtable=0x00000000
Mass bkeep=0 Mass bpawaited=0 Mass btripsleft=0 Mass btripsleft_orig=0
bmass_size=4096 (4K pool) bunref_cnt=0
bmass_stat=0x0800 (0x00000800 (MASS_NOTHASHED))
bbuf_stat=0x0 (0x00000000)
Buffer blpageno=66248 bpg_size=4k Mass blpageno=66248 (Buffer slot #: 0)
bxls_pin=0x00000000 bxls_next=0x00000000 bspid=0
bxls_flushseq=0 bxls_pinseq=0 bcurrxdes=0x00000000
Latch and the wait queue:
Latch (address: 0x28288020)
latchmode: 0x0 (FREE_LATCH)
latchowner: 0
latchnoofowners: 0
latchwaitq: 0x00000000  latchwaitqt: 0x00000000
Latch wait queue:
PAGE HEADER:
Page header for page 0x28287000
pageno=66248 nextpg=0 prevpg=0 ptnid=1431673117  timestamp=0000 0040a817
lastrowoff=62 level=2 indid=2 freeoff=77 minlen=15
page status bits: 0x80 (0x0080 (PG_FIXED))
DATA:
Offset 32 - row length=15 # varlen cols=0 Child page ID=67258
28287020 (     0):  00be8301 00aff000 004100ba 060100    .........A.....
Row-Offset table for variable-length columns:
[<varcol number>, <offset from start of the row>, <varcol length>]
Offset 47 - row length=15 # varlen cols=0 Child page ID=65823
2828702F (     0):  00be8301 00aff000 0041001f 010100    .........A.....
Row-Offset table for variable-length columns:
[<varcol number>, <offset from start of the row>, <varcol length>]
Offset 62 - row length=15 # varlen cols=0 Child page ID=66990
2828703E (     0):  007b0703 00f0f200 009d00ae 050100    .{.............
Row-Offset table for variable-length columns:
[<varcol number>, <offset from start of the row>, <varcol length>]
OFFSET TABLE:
DBCC execution completed. If DBCC printed error messages, contact a user with
System Administrator (SA) role.
1>

此时,可以看出索引根部的级别level为2。 也就是说索引还有中间层level=1和叶子层level=0.

看第二行数据,

Offset 47 - row length=15 # varlen cols=0 Child page ID=65823
2828702F (     0):  00be8301 00aff000 0041001f 010100    .........A.....
Row-Offset table for variable-length columns:
[<varcol number>, <offset from start of the row>, <varcol length>]

它的子页面号是:65823。

再来查看65823的页面数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
1> dbcc page(4,65823,1)
2> go
Page not found in Cache: default data cache.
Page read from disk.
BUFFER:
Buffer header for buffer 0x28288000 (Mass head)
page=0x28287000 bdnew=0x00000000 bdold=0x00000000 bhash=0x00000000
bmass_next=0x00000000 bmass_prev=0x00000000 bdbid=4
bvirtpg= [ 0x28288070 vpgdevno=5 vpvpn=131646 vdisk=0x21DE1CB4 ]
bmass_head=0x28288000 bmass_tail=0x28288000
bcache_desc=0x2828B3F0 (cache name='default data cache')
bpool_desc=0x00000000 bdbtable=0x00000000
Mass bkeep=0 Mass bpawaited=0 Mass btripsleft=0 Mass btripsleft_orig=0
bmass_size=4096 (4K pool) bunref_cnt=0
bmass_stat=0x0800 (0x00000800 (MASS_NOTHASHED))
bbuf_stat=0x0 (0x00000000)
Buffer blpageno=65823 bpg_size=4k Mass blpageno=65823 (Buffer slot #: 0)
bxls_pin=0x00000000 bxls_next=0x00000000 bspid=0
bxls_flushseq=0 bxls_pinseq=0 bcurrxdes=0x00000000
Latch and the wait queue:
Latch (address: 0x28288020)
latchmode: 0x0 (FREE_LATCH)
latchowner: 0
latchnoofowners: 0
latchwaitq: 0x00000000  latchwaitqt: 0x00000000
Latch wait queue:
PAGE HEADER:
Page header for page 0x28287000
pageno=65823 nextpg=66990 prevpg=67258 ptnid=1431673117  timestamp=0000
0040a817
lastrowoff=4052 level=1 indid=2 freeoff=4067 minlen=15
page status bits: 0x80 (0x0080 (PG_FIXED))
DATA:
Offset 32 - row length=15 # varlen cols=0 Child page ID=65822
28287020 (     0):  00be8301 00aff000 0041001e 010100    .........A.....
Row-Offset table for variable-length columns:
[<varcol number>, <offset from start of the row>, <varcol length>]
Offset 47 - row length=15 # varlen cols=0 Child page ID=66249
2828702F (     0):  002f8501 0039f100 003300c9 020100    ./...9...3.....
Row-Offset table for variable-length columns:
[<varcol number>, <offset from start of the row>, <varcol length>]
Offset 62 - row length=15 # varlen cols=0 Child page ID=66250
2828703E (     0):  00a08601 007bf100 00a700ca 020100    .....{.........
Row-Offset table for variable-length columns:
[<varcol number>, <offset from start of the row>, <varcol length>]
Offset 77 - row length=15 # varlen cols=0 Child page ID=66251
2828704D (     0):  00118801 00f3f000 00c800cb 020100    ...............
Row-Offset table for variable-length columns:
[<varcol number>, <offset from start of the row>, <varcol length>]
Offset 92 - row length=15 # varlen cols=0 Child page ID=66252
2828705C (     0):  00828901 00b0f000 00d100cc 020100    ...............
Row-Offset table for variable-length columns:
[<varcol number>, <offset from start of the row>, <varcol length>]
Offset 107 - row length=15 # varlen cols=0 Child page ID=66253
2828706B (     0):  00f38a01 003af100 00b500cd 020100    .....:.........
Row-Offset table for variable-length columns:
[<varcol number>, <offset from start of the row>, <varcol length>]

此页面65823是索引的中间层level=1.看第一行的数据。

Offset 32 - row length=15 # varlen cols=0 Child page ID=65822
28287020 (     0):  00be8301 00aff000 0041001e 010100    .........A.....
Row-Offset table for variable-length columns:
[<varcol number>, <offset from start of the row>, <varcol length>]

页面:66248上的第二行就是指向该行。

继续查看它的子页面上的数据,65822。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
1> dbcc page(4,65822,1)
2> go
Page not found in Cache: default data cache.
Page read from disk.
BUFFER:
Buffer header for buffer 0x28288000 (Mass head)
page=0x28287000 bdnew=0x00000000 bdold=0x00000000 bhash=0x00000000
bmass_next=0x00000000 bmass_prev=0x00000000 bdbid=4
bvirtpg= [ 0x28288070 vpgdevno=5 vpvpn=131644 vdisk=0x21DE1CB4 ]
bmass_head=0x28288000 bmass_tail=0x28288000
bcache_desc=0x2828B3F0 (cache name='default data cache')
bpool_desc=0x00000000 bdbtable=0x00000000
Mass bkeep=0 Mass bpawaited=0 Mass btripsleft=0 Mass btripsleft_orig=0
bmass_size=4096 (4K pool) bunref_cnt=0
bmass_stat=0x0800 (0x00000800 (MASS_NOTHASHED))
bbuf_stat=0x0 (0x00000000)
Buffer blpageno=65822 bpg_size=4k Mass blpageno=65822 (Buffer slot #: 0)
bxls_pin=0x00000000 bxls_next=0x00000000 bspid=0
bxls_flushseq=0 bxls_pinseq=0 bcurrxdes=0x00000000
Latch and the wait queue:
Latch (address: 0x28288020)
latchmode: 0x0 (FREE_LATCH)
latchowner: 0
latchnoofowners: 0
latchwaitq: 0x00000000  latchwaitqt: 0x00000000
Latch wait queue:
PAGE HEADER:
Page header for page 0x28287000
pageno=65822 nextpg=66249 prevpg=65821 ptnid=1431673117  timestamp=0000
0040a817
lastrowoff=4080 level=0 indid=2 freeoff=4091 minlen=11
page status bits: 0x82 (0x0080 (PG_FIXED), 0x0002 (PG_LEAF))
DATA:
Offset 32 - row length=11 # varlen cols=0 Data page RID=(61615, 65)
28287020 (     0):  00be8301 00aff000 004100             .........A.
Row-Offset table for variable-length columns:
[<varcol number>, <offset from start of the row>, <varcol length>]
Offset 43 - row length=11 # varlen cols=0 Data page RID=(61752, 198)
2828702B (     0):  00bf8301 0038f100 00c600             .....8.....
Row-Offset table for variable-length columns:
[<varcol number>, <offset from start of the row>, <varcol length>]
Offset 54 - row length=11 # varlen cols=0 Data page RID=(61818, 239)
28287036 (     0):  00c08301 007af100 00ef00             .....z.....
Row-Offset table for variable-length columns:
[<varcol number>, <offset from start of the row>, <varcol length>]

还是分析页面上的第一行。索引叶子层直接指向了数据页面,某一页面上的某一行。

Offset 32 - row length=11 # varlen cols=0 Data page RID=(61615, 65)
28287020 (     0):  00be8301 00aff000 004100             .........A.
Row-Offset table for variable-length columns:
[<varcol number>, <offset from start of the row>, <varcol length>]

继续往下,dbcc page(4,61615,1)找到第65行数据。

Offset 1072 - row ID=65 row length=16 # varlen cols=1
28287430 (     0):  0141be83 0100 1000 7a68616e 67020d08  .A......zhang...
28287440 (    16):
Row-Offset table for variable-length columns:
[<varcol number>, <offset from start of the row>, <varcol length>]
[1, 8, 5]

可以看出00be8301 00 aff000 004100中的be8301 00 和0141be83 01001000 7a68616e 67020d08中的

be83 0100 是一致的。也就是说索引中间层level=1是指向叶子层上的第一页。并且包含索引键的数据99262。

从16进制数据中分析出来,页61615第65行的数据为:id=99262,name='zhang'。

暂时就分析这么多。可能有些地方说的不太明白。见谅!

————————————————————————————————
—- 本文为andkylee个人原创,请在尊重作者劳动成果的前提下进行转载;
—- 转载务必注明原始出处 : http://www.dbainfo.net
—- 关键字:索引 存储结构 分析  非聚簇 index storage non-clustered
————————————————————————————————

SQL SERVER 如何读取页面上的数据

CSDN上有篇帖子http://topic.csdn.net/u/20100223/15/644e6212-9fdc-42de-81ad-785d28ed71d3.html 讨论查询计划读取索引页的问题。

主要问题是: 在一张建有聚集索引(没有其它非聚集索引)的表执行select count(*) 。查询显示扫描了所有的索引中间层叶。从理论上讲,由于每个数据页都记录着它的上一个page和下一个page,那么最小的io读取应该是:
读根页--->读最小的中间页--->读数据叶子---->依次往后读全部数据页.

但实际上却是读取全部的索引中间页。

帖子中比较精彩的回复:

1
2
3
4
5
首先楼主的说法不够准确,根据我的测试结果来看,更准确的说法应该是sql server在做索引全扫(INDEX FULL scan)时,会先扫描全部的level 1的节点,然后在扫描处于level 0的叶节点。也就是说一次索引全扫的顺序是(这个顺序对于clustered INDEX和普通INDEX都是一样的):
读根页--->读最小的中间页(level 1以上的层)--->依次读level 1的所有节点的索引页---->读全部数据页
上述顺序可以在将数据表索引层数增加到3层以上时得到验证。
实际上我觉得sql server这样做是很有道理的,楼主认为的是所读的页越少,io就越小,这是不对的。对于sql server来说,一次io并不一定只读取一个页,很多时候特别是做表全扫或索引全扫时,sql server都会尽量的让一次io读取尽可能多的数据页,这样才能较少io的次数。为达到这个目的,sql server首先就要知道它到底需要读取那些页,知道要读取页的分布之后,就可以安排io调度器尽可能的将临近的数据页用一次io读取上来,而要知道需要读取数据页的分布就需要先读取到level 1上的所有页,以找出要读取io页的分布情况,然后再做最优的io读取安排。
同样的道理,如果sql server是先定位到叶节点的起始数据页,然后从依次的读取所有的数据页的话,那sql server就只能一次io读取一个数据页,然后找出对应的下一页的指针,再去读取下一个数据页,这样实际上花费的io会远比批量读取数据页多很多的。这就好比我们在做表的lookup时,如果需要lookup的行非常之多的话sql server会转而选择表扫描来达到目的的。

预读

我觉得SQL Server内部存在这样一个机制:
它会尽量将当前使用的及使用最频繁的表的数据都带到buffer中来,这跟buffer的算法有一定的关系。
清掉buffer後,你不做任何动作,隔几秒钟,sql会自动将master库中一些比较重要的表都带到buffer中,或者
你select * from tb where 1 <>1 ,尽管该查询不会扫描任何数据,但sql还是会自动将tb的所有索引页到带到
buffer中去,因为它可能觉得tb这个表後续可能会使用到.

关于计算逻辑读的数量

IAM页是否会计算在逻辑读中我都是持怀疑态度的。
比如说,一个堆表扫描时肯定是从IAM页开始的,然而用set statistics io on看到的逻辑读数量却只是表所占用的总页数,并没有加上相应的IAM页数量。
而索引扫描却更有意思,就像楼主说的那样,会比我们计算的数据页加索引页的页数还要多1个,但这1个却不知是从何而来的。按照堆表扫描的计算方法,我认为更不可能是IAM页,再说索引扫描也不会用到IAM页。
一直没有看到有描述逻辑读怎么计算的资料。
在没有详细资料的情况下要研究的这么精确,不纠结才怪

实际上: 是sql server 内部引擎设计上的问题。sql server 利用预读机制来改善IO。通过读取所有的索引中间层叶sql server能够知道预读哪些页面,而不是利用数据页面上的前后链接指针那样一页一页的读。 sql server 最大一次IO可以读取64页。

以下是从msdn上摘下来的帮助文档。

SQL Server 2008 联机丛书(2009 年 7 月)

读取页

SQL Server 数据库引擎实例的 I/O 包括逻辑读取和物理读取。每次数据库引擎从缓冲区高速缓存 请求页时都会发生逻辑读取。如果页当前不在缓冲区高速缓存中,物理读取将首先将页从磁盘复制到缓存中。

数据库引擎实例生成的读取请求由关系引擎控制,并由存储引擎优化。关系引擎决定最有效的访问方法(例如,表扫描、索引扫描或键读取);存储引擎的访问方法和缓冲区管理器组件确定要执行的读取的常规模式,并对实现访问方法所需的读取进行优化。执行批处理的线程将安排读取。

预读

数据库引擎支持称为“预读”的性能优化机制。预读首先预测执行查询执行计划所需的数据和索引页,然后在查询实际使用这些页之前将它们读入缓冲区高速缓存。这样可以让计算和 I/O 重叠进行,从而充分利用 CPU 和磁盘。

预 读机制允许数据库引擎从一个文件中读取最多 64 个连续页 (512KB)。该读取作为缓冲区高速缓存中相应数量(可能是非相邻的)缓冲区的一次散播-聚集读取来执行。如果此范围内的任何页在缓冲区高速缓存中已存 在,当读取完成时,所读取的相应页将被放弃。如果相应页在缓存中已存在,也可以从任何一端“裁剪”页的范围。

有两种类型的预读:一种用于数据页,一种用于索引页。

读取数据页

用 于读取数据页的表扫描在数据库引擎中非常有效。SQL Server 数据库中的索引分配映射 (IAM) 页列出了表或索引使用的区。存储引擎可以读取 IAM 以生成必须读取的磁盘地址的排序列表。这使得存储引擎能够根据要读取的磁盘位置,将其 I/O 操作优化为按顺序执行的大型顺序读取。有关 IAM 页的详细信息,请参阅管理对象使用的空间

读取索引页

存储引擎按键的顺序依次读取索引页。例如,下图显示了一组叶级页的简化表示法,该组叶级页包含映射叶级页的键集和中间索引节点。有关索引中页的结构的详细信息,请参阅聚集索引结构

存 储引擎使用高于叶级的中间索引页上的信息为包含键的页安排序列预读。如果请求针对的是 ABC 到 DEF 之间的所有键,则存储引擎将首先读取高于叶级页的索引页,但它并不是仅仅按顺序读取页 504 到页 556(即指定范围内的包含键的最后一页)之间的每个数据页。相反,存储引擎将扫描中间索引页并生成必须要读取的叶级页的列表。然后,存储引擎会按键的顺 序安排所有读取。存储引擎还会识别出页 504/505 以及页 527/528 是相邻页,并执行一次散播读取,从而在单个操作中检索这些相邻页。如果在一个序列操作中要检索许多页,则存储引擎将一次安排一个读取块。完成这些读取子集 后,存储引擎将安排同等数量的新读取,直到安排完所需的全部读取。

存储引擎使用预提取加快非聚集索引的基表查找。 非聚集索引的叶级行包含指针,指向含有每个特定键值的数据行。存储引擎浏览非聚集索引的叶级页时,它也会开始计划异步读取已检索了其指针的数据行。这可以 使存储引擎在完成非聚集索引的扫描之前从基础表中检索数据行。无论表是否有聚集索引,都会使用预提取。SQL Server Enterprise 比 SQL Server 其他版本使用更多的预提取,可以预读更多页。在任何版本中都无法配置预提取的级别。有关非聚集索引的详细信息,请参阅非聚集索引结构

高级扫描

在 SQL Server Enterprise 中,高级扫描功能使得多项任务可以共享完全表扫描。如果 Transact-SQL 语句的执行计划需要扫描表中的数据页,并且数据库引擎检测到其他执行计划正在扫描该表,则数据库引擎会在第二个扫描的当前位置将第二个扫描加入第一个扫 描。数据库引擎会一次读取一页,并将每一页的行传递给这两个执行计划。此操作将一直持续到该表的结尾处。

此时,第一个执行 计划已有完整的扫描结果,而第二个执行计划仍必须检索在它加入正在进行的扫描之前读取的数据页。然后,第二个执行计划中的扫描将绕回到表的第一个数据页, 并从这里向前扫描到它加入第一个扫描时所处的位置。可以按这种方式组合任意数量的扫描。数据库引擎将循环遍历数据页,直到完成所有扫描。这种机制也称为 “走马灯式扫描”,说明了为何在没有 ORDER BY 子句的情况下无法保证 SELECT 语句所返回结果的顺序。

例 如,假设某个表有 500,000 页。UserA 执行了一条 Transact-SQL 语句,要求对该表进行扫描。当扫描已处理了 100,000 页时,UserB 执行了另一条 Transact-SQL 语句,要对同一个表进行扫描。数据库引擎将为页 100,001 之后的页安排一组读取请求,并将每页中的行同时传递回两个扫描。当扫描到页 200,000 时,UserC 执行了另一条 Transact-SQL 语句,要对同一个表进行扫描。则从页 200,001 开始,数据库引擎将把它读取的每一页中的行传递回所有三个扫描。当数据库引擎读取完第 500,000 行之后,UserA 的扫描就完成了,而 UserB 和 UserC 的扫描将绕回到页 1 开始读取。当数据库引擎到达页 100,000 时,UserB 的扫描就完成了。然后 UserC 的扫描将继续进行,直到它读取完页 200,000。此时,所有扫描便均已完成。

在没有高级扫描的情况下,每个用户都必须要争用缓冲区空间并因此导致磁盘臂争用。然后,会分别为每个用户读取一次相同的页,而不是一次读取并由多个用户共享,这样会降低性能并加重资源负担。


SQL Server Architecture (SQL Server 2000)
Reading Pages

The read requests generated by an instance of Microsoft® SQL Server™ 2000 are controlled by the relational engine and further optimized by the storage engine. The access method used to read pages from a table, such as a table scan, an index scan, or a keyed read, determines the general pattern of reads that will be performed. The relational engine determines the most effective access method. This request is then given to the storage engine, which optimizes the reads required to implement the access method. The thread executing the batch schedules the reads.

Table scans are extremely efficient in SQL Server 2000. The IAM pages in a SQL Server 2000 database list the extents used by a table or index. The storage engine can read the IAM to build a sorted list of the disk addresses that must be read. This allows SQL Server 2000 to optimize its I/Os as large sequential reads that are done in sequence based on their location on the disk. SQL Server 2000 issues multiple serial read-ahead reads at once for each file involved in the scan. This takes advantage of striped disk sets. SQL Server 2000 Enterprise Edition dynamically adjusts the maximum number of read ahead pages based on the amount of memory present; it is fixed in all other editions of SQL Server 2000.

One part of the SQL Server 2000 Enterprise Edition advanced scan feature allows multiple tasks to share full table scans. If the execution plan of a SQL statement calls for a scan of the data pages in a table, and the relational database engine detects that the table is already being scanned for another execution plan, the database engine joins the second scan to the first, at the current location of the second scan. The database engine reads each page once and passes the rows from each page to both execution plans. This continues until the end of the table is reached. At that point, the first execution plan has the complete results of a scan, but the second execution plan must still retrieve the data pages that occur before the point at which it joined the in-progress scan. The scan for second execution plan then wraps back to the first data page of the table and scans forward to the point at which it joined the first scan. Any number of scans can be combined in this way, the database engine will keep looping through the data pages until it has completed all the scans.

For example, assume that you have a table with 500,000 pages. UserA executes a SQL statement that requires a scan of the table. When that scan has processed 100,000 pages, UserB executes another SQL statement that scans the same table. The database engine will schedule one set of read requests for pages after 100,001, and passes the rows from each page back to both scans. When the scan reaches the 200,000th page, UserC executes another SQL statement that scans the same table. Starting with page 200,001, the database engine passes the rows from each page it reads back to all three scans. After reading the 500,000th row, the scan for UserA is complete, and the scans for UserB and UserC wrap back and start reading pages starting with page 1. When the database engine gets to page 100,000, the scan for UserB is complete. The scan for Userc then keeps going alone until it reads page 200,000, at which point all the scans have been completed.

Reading Index Pages

SQL Server 2000 reads index pages serially in key order. For example, this illustration shows a simplified representation of a set of leaf pages containing a set of keys and the intermediate index node mapping the leaf pages.

SQL Server 2000 uses the information in the intermediate index page above the leaf level to schedule serial read-ahead I/Os for the pages containing the keys. If a request is made for all the keys from 'ABC' to 'DEF', the instance of SQL Server 2000 first reads the index page above the leaf page. It does not, however, simply read each individual data page in sequence from page 504 to page 556, the last one with keys in the desired range. Instead, the storage engine scans the intermediate index page and builds a list of the leaf pages that must be read. The storage engine then schedules all the I/Os in key order. The storage engine also recognizes that pages 504/505 and 527/528 are contiguous, and performs a single scatter-gather read to retrieve the adjacent pages in one operation. When there are many pages to be retrieved in a serial operation, SQL Server schedules a block of reads at a time. When a subset of these reads is completed, SQL Server schedules an equal number of new reads until all the needed reads have been scheduled.

SQL Server 2000 uses pre-fetching to speed the processing of non-clustered indexes. The leaf rows of a non-clustered index contain pointers to the data rows containing each specific key value. As the database engine reads through the leaf pages of the non-clustered index, it also starts scheduling asynchronous reads for the data rows whose pointers have already been retrieved. This allows the database engine to start retrieving rows before it has completed the scan of the non-clustered index. This process is followed regardless of whether or not the table has a clustered index. SQL Server 2000 Enterprise Edition uses more pre-fetching than other editions of SQL Server, and the level of pre-fetching is not configurable in any edition.

MSSQLSERVER利用日志恢复drop table的表数据[转]

-- 创建测试数据库
CREATE DATABASE Db
GO

-- 对数据库进行备份
BACKUP DATABASE Db TO DISK = ' c:\db.bak ' WITH FORMAT
GO

-- 创建测试表
CREATE TABLE Db.dbo.TB_test(ID int )

-- 延时1秒钟,再进行后面的操作(这是由于SQL Server的时间精度最大为百分之三秒,不延时的话,可能会导致还原到时间点的操作失败)
WAITFOR DELAY ' 00:00:01 '
GO

-- 假设我们现在误操作删除了 Db.dbo.TB_test 这个表
DROP TABLE Db.dbo.TB_test

-- 保存删除表的时间
SELECT dt = GETDATE () INTO #
GO

-- 在删除操作后,发现不应该删除表 Db.dbo.TB_test

-- 下面演示了如何恢复这个误删除的表 Db.dbo.TB_test

-- 首先,备份事务日志(使用事务日志才能还原到指定的时间点)
BACKUP LOG Db TO DISK = ' c:\db_log.bak ' WITH FORMAT
GO

-- 接下来,我们要先还原完全备份(还原日志必须在还原完全备份的基础上进行)
RESTORE DATABASE Db FROM DISK = ' c:\db.bak ' WITH REPLACE ,NORECOVERY
GO

-- 将事务日志还原到删除操作前(这里的时间对应上面的删除时间,并比删除时间略早
DECLARE @dt datetime
SELECT @dt = DATEADD (ms, - 20 ,dt) FROM #  -- 获取比表被删除的时间略早的时间
RESTORE LOG Db FROM DISK = ' c:\db_log.bak ' WITH RECOVERY,STOPAT = @dt
GO

-- 查询一下,看表是否恢复
SELECT * FROM Db.dbo.TB_test

/* --结果:
ID
-----------

(所影响的行数为 0 行)
-- */

-- 测试成功
GO

-- 最后删除我们做的测试环境
DROP DATABASE  Db
DROP TABLE #