红俊's profile蝈蝈俊的共享空间PhotosBlogListsMore ![]() | Help |
|
January 24 八年昨天,由Sisley Lin 处得知,我将被推荐成了“十大杰出开发技术英雄”候选人之一,有点激动。推荐的理由是我为CSDN 这个最大的中文技术社区的成长所贡献的这些年。当我回首看自己在CSDN经历时,发现自己已为CSDN公司工作了7年半,在CSDN 论坛活动了8年。蓦然回首,八年光阴已匆匆而过,心中真是感慨万千呀。 记得那是在99年底,2000年初时,我从大学毕业刚半年,跟随一个我的铁哥们跳槽到一家新成立的电子商务的网站(http://www.ego88.com/, 不过这个网站现在已经无法访问了)。由于之前我是在一家使用Delphi开发ERP,医院信息系统的公司,我对Delphi非常感兴趣,当时最常去的就是大富翁论坛(http://www.delphibbs.com/)。经这次跳槽后,由于转行做网站,就开始学习ASP技术。在大富翁论坛时,听说新开了一个程序员的论坛CSDN,而CSDN有个板块在讨论ASP,于是乎,就整天泡CSDN论坛的ASP版,边学习,边帮别人解答问题。慢慢的,自己的ASP水平也提高起来了,后来就成了ASP版的最早一任版主。那时候,也达到了自己在CSDN专家榜的最高排名——CSDN总专家榜排名13。 2000年6月时,由于我在EGO88网站经常跟技术经理发生技术争执,被开除了。痛定思痛之后,我决定离开杭州,来北京发展,没想到这次离开成了我人生的转折点。恰巧当时CSDN网站刚刚被百联集团收购,准备扩展业务,正在不断招纳新人,于是我便和曾登高联系了一下, 7月15日就来到了北京,从此就开始了我为CSDN网站工作的历程。 还记得当时CSDN公司在亚运村,就是现在鸟巢的位置,而我就租住在旁边的惠忠北里小区。每天早上,我都从小区出来,向北跑步,就是现在的大屯路一带,全是麦田和菜地。不想经过这五六年的发展,现在这一片的房价均价已达到一万元以上……. 我在CSDN负责的第一个项目是文档中心(http://dev.csdn.net/ 现在这里已经不归我负责了,也不是我当初做的那个风格了),就是现在博客的前身,不知道还有多少人记得这个产品。当时为了测试和运作文档中心,我从2000年7月开始,疯狂转贴了很多文章。而文档中心文章在升级成博客后,全部迁移到博客中了,这还一度造成我的博客发文数是第一。 随后,在从事了其他一些小的工作后,我就接手了CSDN论坛,当时那一版本的论坛仍然是ASP开发的,采用了双缓存机制,同时静态帖子采用了XML+XSL技术。说起来你可能不相信,那一个版本的论坛是完全由我一个人开发完成的。 那可是单枪匹马闯五关的时代呀,由于我最早使用XML技术,最初时有些技术难题没能解决,一度被Boss批判说,不要使用不成熟的技术。2003年时,就是我创建的这个版本的论坛达到了历史最高在线人数,同时发帖量,回复量都相当高。要知道,当时的论坛只是两台服务器,一台WEB,一台数据库。当时服务器的配置也只是2G内存+双CPU,志强2.4G,能做到那个成绩实属不易。 04年是非常郁闷的一年,现在看来,04年忙忙碌碌的开发了很多项目,但是这些项目都荒废了,不再看得到了。03年下半年开始,我开发的项目都转到ASP.net 技术了,虽然这些项目最终由于运维等原因,流产了,但是至少也算是从那时候开始对.net 技术有了较深的了解吧。 05年的时候,已经开始基于.net技术开发新版论坛了。但是,由于不受公司重视,开发到一半竟被中止,把人员抽调到其他项目,使得无法继续。直到06年的时候,由于论坛在03年9月达到高峰后,一直走下坡路,公司终于决定在论坛方面增大力度。而新的论坛开发已经不再是单枪匹马就可以搞定的了,需要团队协作,而组建的团队又问题多多,磕磕碰碰后,终于在06年底发布了群组。群组使用的是跟论坛一样的底层架构。之后采用里程碑增量开发模式,终于在07年9月28日,把所有老论坛升级到了新版论坛。之后,就是一系列功能的补充完善,优化。随着公司投入力量的加强,我预计,也希望08年论坛又会象03年一样,达到一个新的高峰。 回首过去这八年,我很感谢CSDN为我提供了一片成长的平台。CSDN伴着我成熟,我也见证了CSDN的成长。随着CSDN的不断改进,我自己也从菜鸟成长起来。我的角色也从最初的程序员变成高级程序员,再变成开发经理。(我在CSDN注册时的ID是4807,也就是我之前CSDN用户只有 不到5000个,而截至2007年底,CSDN注册用户已经达到250万多了。)这些成长都是通过自己的努力来实现的。一分耕耘,一分收获,我在CSDN实现了我的第一个“孩子之梦”…… CSDN 论坛有很多有意思的故事。比如:女英雄无为;彭大力追旋玑;拉萨mm挖EDYang的故事等等,而这一个个故事都发生在自己一手搭建的论坛平台上,其中的成就感不言而喻。这也是我能在CSDN这家公司工作7年多的根本原因,我已经把它当成了我生活的一部分,无法割舍。 八年时光,弹指即过,人生能有几个八年?八年时间我做了不少事情,但仅凭一手搭建了CSDN论坛这个平台,此八年不算虚度。希望未来的八年,我的程序员人生将更加精彩。 仅以本文来怀念我患得患失的八年。 郭红俊 2008年1月10日 星期四 于北京
附,微软开发技术英雄,IT技术英雄的投票地址在 http://www.microsoft.com/zh/cn/default.aspx 的最大的那个flash 广告。 具体地址就是:http://www.hhhchina.net/ 。 需要在中国地图上找到某个人才可以投票,郁闷的是,竟然地图上把我丢到内蒙了。 拉票了,大家都来投我一票啦!^&^ January 23 SQL Server 索引基础知识(10)----Joins 时的三种算法简介我们书写查询语句的时候,Join 参数之前可以是下面三个 { LOOP | MERGE | HASH } JOIN 。 如果不使用,则系统自己分析那种方式快,使用那种方式。 这其实是SQL Server 联结时候使用的三种算法。尽管每种算法都并不是很复杂,但考虑到性能优化,在产品级的优化器实现时往往使用的是改进过的变种算法。譬如SQL Server 支持block nested loops、index nexted loops、sort-merge、hash join以及hash team。我们在这里只对上述三种基本算法的原型做一个简单的介绍。
知识点: Tables join总是两个两个进行的。所以下面的算法都是两个表的联结。
分别使用这三种 Join 的例子: create table t1 ( i int not null ) set showplan_text on alter table t1 add primary key ( i )
参考资料: 浅谈查询优化器中的JOIN算法
Example of merge ,hash and nested join
Inside SQL Server Joins January 18 SQL Server 索引基础知识(9)----Indexing for OR我们仍然是通过例子来理解OR运算符的特征 我们仍然使用 http://blog.joycode.com/ghj/archive/2008/01/18/113870.aspx 中的 member 表,这时候,这个表的索引如下:
我们执行下面的查询 SELECT m.LastName, m.FirstName, m.Region_No 我们用另外一个SQL来模拟 SQL 的执行计划,就是下面的语句: select m.LastName, m.FirstName, m.Region_No这两个查询的扫描计数均是 2,逻辑读取均是 10 次。 他们俩个的执行计划如下图,点击看大图:
知识点小结: OR 会做什么?
上面的例子中,我们用 union 来演示 or的情况。 OR和UNION相似。不同之处如下:
OR 的一个简单应用就是 In 关键字。
SQL Server 索引基础知识(8)--- 数据基本格式补充我在SQL Server 索引基础知识系列中,第一篇就讲了记录数据的基本格式。那里主要讲解的是,数据库的最小读存单元:数据页。一个数据页是8K大小。 对于数据库来说,它不会每次有一个数据页变化后,就存到硬盘。而是变化达到一定数量级后才会作这个操作。 这时候,数据库并不是以数据页来作为操作单元,而是以64k的数据(8个数据页,一个区)作为操作单元。 区是管理空间的基本单位。一个区是八个物理上连续的页(即 64 KB)。这意味着 SQL Server 数据库中每 MB 有 16 个区。 为了使空间分配更有效,SQL Server 不会将所有区分配给包含少量数据的表。SQL Server 有两种类型的区:
通常从混合区向新表或索引分配页。当表或索引增长到 8 页时,将变成使用统一区进行后续分配。如果对现有表创建索引,并且该表包含的行足以在索引中生成 8 页,则对该索引的所有分配都使用统一区进行。
为何会这样呢? 其实很简单: 读或写 8KB 的时间与读或写 64 KB的时间几乎相同。 参看:
参考资料: MSDN 中关于“页和区”的描述 SQL Server 索引基础知识(7)----Indexing for AND我们通过一个实例来看 有And 操作符时候的最常见的一种情况。我们有下面一个表, CREATE TABLE [dbo].[member]( 这个表具备下面的四个索引:
当我们执行下面的SQL查询时候, SELECT m.Member_No, m.FirstName, m.Region_No SQL Server 会根据索引方式,优化成下面方式来执行。 select a.Member_No,a.FirstName,b.Region_No 不信,你可以看这两个SQL 的执行计划,以及逻辑读信息,都是一样的。 其实上面的SQL,如果优化成下面的方式,实际的逻辑读消耗也是一样的。为何SQL Server 不会优化成下面的方式。是因为 and 操作符优化的另外一个原则。 1/26 的数据和 1/6 的数据找交集的速度要比 1/52 的数据和 1/3 的数据找交集速度要慢。
当然,我们要学习SQL 如何优化的话,就会用到查询语句中的一个功能,指定查询使用哪个索引来进行。 比如下面的查询语句 SELECT m.Member_No, m.FirstName, m.Region_No 这里 Index 计算符可以是 0 ,1, 指定的一个或者多个索引名字。对于 0 ,1 的意义如下: 如果存在聚集索引,则 INDEX(0) 强制执行聚集索引扫描,INDEX(1) 强制执行聚集索引扫描或查找(使用性能最高的一种)。 总结知识点:
参考资料 本文演示代码下载地址: January 16 SQL Server 索引基础知识(6)----索引的代价,使用场景前几天给同事培训了聚集索引,非聚集索引的知识后,在一个同事新作的项目中,竟然出现了滥用聚集索引的问题。看来没有培训最最基础的索引的意义,代价,使用场景,是一个非常大的失误。这篇博客就是从这个角度来罗列索引的基础知识。
使用索引的意义
使用索引的代价
创建索引的列
不创建索引的列
Heaps是staging data的很好选择,当它没有任何Index时
聚集索引提高性能的方法,在前面几篇博客中分别提到过,下面只是一个简单的大纲,细节请参看前面几篇博客。 何时创建聚集索引? Clustered Index会提高大多数table的性能,尤其是当它满足以下条件时:
聚集索引唯一性(独特型的问题)
参看我的这篇博客: SQL Server 索引基础知识(4)----主键与聚集索引 聚集索引持续向上增长的需求
细节参看我的这篇博客: SQL Server 索引基础知识(5)----理解newid()和newsequentialid() 至于,如果你的数据已经存在重复了,而且是不应该出现的,则可以参看下面这篇KB : 如何删除 SQL Server 表中的重复行
非聚集索引提高性能的方法 非聚集索引由于B+树的节点不是具体数据页,有时候由于这个原因,会导致非聚集索引甚至不如表遍历来的快,参看我在下面这篇博客中给的例子 但是,非聚集索引有个特性,如果你要查询的内容,在非聚集索引中以及被覆盖到了,则不需要继续到聚集索引,或者RID中去寻找数据了,这时候就可以很大的提高性能,这就是 覆盖面(Covering) 的问题。
由于聚集索引叶子节点就是具体数据,所以 聚集索引的覆盖率是 100%, 通过提高覆盖面来提高性能的问题也就只有非聚集索引(Nonclustered Indexes)才存在。 当查询中所有的columns 都包括在index上时,我们说这个 index covers the query. Columns的顺序在此不重要 (Select 时候的顺序不重要,但是Index 建立的顺序可得小心了)。
在 SQL Server 2005 中,为了提高这种 Covering 带来的好处,甚至 可以通过将非键列添加到非聚集索引的叶级别来扩展非聚集索引的功能。 比如下面的脚本, 虽然我们是对 Title, Revision 建立的非聚集索引,但是这个非聚集索引的叶子节点上还包含 FileName 字段的信息。 USE AdventureWorks; 下面的代码就是测试 Covering 的. 我已经在每个查询使用的方式,逻辑读的个数都标在每个查询前面了。
参考资料 Teched 2007 上 吴家震 主讲的"微软SQL服务器Always-On Tech-nologies: 高级索引策略" 录像下载地址: January 14 ASP.net 2.0 中 WebResource.axd 管理资源的一些知识点在 ASP.net 2.0 构建的Web页面中,查看源文件,我们经常会看到下面的Html文本 <script src="/WebResource.axd?d=QfRKDnWw93T08KaF3ioSKQ2&t=633313193233609691" type="text/javascript"></script> <script src="/WebResource.axd?d=9iVKU5SS0wd5al1SYg8zjL8XXbP97LbENHerY4aLtJk1&t=633313193233609691" type="text/javascript"></script> 这是 ASP.net 2.0 提供的新的资源管理方式产生的脚本。 新的资源管理方式如何使用,你可以参看以下几篇博客: 使用ASP.NET 2.0提供的WebResource管理资源 在自定义Server Control中捆绑JS文件 Step by Step 使用 ASP.NET 2.0 中 Web 资源 http://www.cnblogs.com/yeahooh/archive/2007/07/27/833846.html
使用 WebResource管理资源时, 我们会经常收到类似下面的异常: System.Web.HttpException: 无效的视图状态。 比如下面几个文章就提到了这个问题: Annoying CryptographicException on WebResource.axd ASP.Net’s WebResource.axd and machineKey badness
这是因为 WebResource.axd URL 的参数具有时效性,但是对于搜索引擎的爬虫来说,他们会经常访问这些参数过期的地址,所以就会出现上面的异常。 这个问题的解决方案,目前没有更好的方案,微软论坛中只是建议在robots.txt 文件中增加下面的信息: User-agent: * 但是这要求遵循 robots.txt 规范的爬虫们下次获得最新的 robots.txt 才会起作用。而对于那些不遵循 robots.txt 规范的爬虫,可一点办法都没有。
我现在的想法是,能不用 WebResource.axd 就不要用,因为国内不遵循robots.txt 规范的爬虫们太多了。这就需要我们来分析那些场景使用了 WebResource.axd 中的资源,也就是 需要对 WebResource.axd 的格式进行分析。
WebResource.axd 的 URL 的格式是: WebResource.axd?d=encrypted 标识符 & T = 时间戳值。 " d " 代表请求 Web 资源。 (encrypted identifier)
t 参数对于我们分析谁使用它,没有意义,我们下面就来分析 d 参数。
d 参数的解析代码如下: <%@ Page Language="C#" AutoEventWireup="true" %> <script runat="server"> public static string PageDecryptString(string input) { Type type = typeof(System.Web.UI.Page); object o = Activator.CreateInstance(type); System.Reflection.MethodInfo mi = type.GetMethod("DecryptString", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static, null, new Type[] { typeof(string) }, null); object result = mi.Invoke(o, new object[] { input }); return result.ToString(); } protected void btn_Post_Click(object sender, EventArgs e) { this.l_Info.Text = PageDecryptString(HttpUtility.UrlDecode(tb_WebResourceDValue.Text)); } </script> <html xmlns="http://www.w3.org/1999/xhtml"> <body> <form id="form1" runat="server"> <asp:TextBox ID="tb_WebResourceDValue" runat="server" /><br /> <asp:Label ID="l_Info" runat="server" /><br /> <asp:Button ID="btn_Post" runat="server" Text="计算" OnClick="btn_Post_Click" /> </form> </body> </html> 我们在 C:\Windows\Microsoft.NET\Framework64\v2.0.50727\CONFIG\web.config 文件中,可以看到 WebResource.axd 文件是配置的通过下面 HttpHandle 来解析的: <add path="WebResource.axd" verb="GET" type="System.Web.Handlers.AssemblyResourceLoader" validate="True"/> 在 System.Web.Handlers.AssemblyResourceLoader 类中,使用 Reflector 工具看,又可以看到下面代码:
void IHttpHandler.ProcessRequest(HttpContext context) { // ... string str = context.Request.QueryString["d"]; // .... string str2 = Page.DecryptString(str); // ... } 显然, d 的参数, 是应该通过 Page.DecryptString 函数来解析的。 而 Page.DecryptString 函数 中,涉及到调用 web 配置中配置的默认加密key。 简单起见,我们这里的解析方法就用 ASP.net 页面来实现了。由于 Page.DecryptString 函数是 internal static 的, 我们上面代码就用反射来调用这个函数,就会获得我们期望的值。 (为了保证解密算法的解密key一致,最简单的做法就是我们把上面这个解密ASPX页面跟需要解析的放在同一个服务器上) 上面 的 QfRKDnWw93T08KaF3ioSKQ2 解密的结果是: s|WebForms.js 竖线只是用于分隔字符串中不同的值。“s”表示该数据为脚本,“WebForms.js”是要检索的资源名称。WebForms.js 资源可从 System.Web.dll 检索。 同理上面 的 9iVKU5SS0wd5al1SYg8zjL8XXbP97LbENHerY4aLtJk1 解密的结果是 : s|WebUIValidation.js 显然如果出现这样的 WebResource.axd 调用,应该是验证控件在调用. 我们要想上面的 <script src="/WebResource.axd?d=QfRKDnWw93T08KaF3ioSKQ2&t=633313193233609691" type="text/javascript"></script>
参考资料: 全球化就绪: 和 ASP.NET AJAX 应用程序环游地球 -- MSDN Magazine, January 2008 对于Asp.Net 2.0中脚本资源的研究(1) 对于Asp.Net 2.0中脚本资源的研究(2) January 08 SQL Server 索引基础知识(5)----理解newid()和newsequentialid()在SQL Server 2005 中新增了一个函数:newsequentialid(),MSDN 中对这个函数的描述如下: 在指定计算机上创建大于先前通过该函数生成的任何 GUID 的 GUID。 这个函数的具体用法在下面这篇博客中已经有详细的描述了。
简单来说,newsequentialid 函数比起 newid 函数最大的好处是:
上面是一个粗略的描述,下面是比较详细点的解释: (我们这里解释的更详细一些,是为了让大家对索引的基础知识了解得更深入些。)
B+ 树不考虑层级变化,增加数据的情况分以下几种情况: The insert algorithm for B+ Trees
更多 B+ 树的算法请参看后面链接: http://www.sci.unich.it/~acciaro/bpiutrees.pdf 对于数据库的索引来说,上面情况中,第三种情况发生的概率很低,更多的是 1,2 这两种情况。
而找到这几个节点后的操作,在实际上,都不是性能消耗最大的地方。性能消耗最大的地方在于搜索找到需要操作的叶子节点。
对于 B+ 树来说, 几层的B+ 树,找到叶子节点就需要找几个数据页。那为何说 Guid 有规律时速度要比无规律时候快呢? 原因很简单: 1、缓存的命中率问题 当每次产生的Guid是有规律时,找到需要操作的叶子节点的几个中间节点,可能已经在之前的访问中被缓存了。 这样,系统不需要大量的读入缓存命中率很低的索引数据页,这样可以节省内存,同时提高搜索速度。
2、连续和不连续的磁盘 I/O 操作对性能的影响 我们都知道,现在很多业务逻辑的瓶颈是硬盘的速度。而硬盘速度提升的空间仍然不大。下面对硬盘读写操作的一些法则对我们优化跟硬盘I/O有关的方面很有帮助。 请记住下面的经验法则:标准的 Wide Ultra SCSI-3 硬盘每秒钟可为 Windows 和 SQL Server 提供 75 个不连续(随机)的 I/O 操作和 150 个连续的 I/O 操作。这种硬盘的标称传输率在 40 MB/秒左右。请记住更有可能限制数据库服务器的传输率是每秒钟 75/150 I/O,而不是 40 MB/秒。 读/写磁头和相关的磁盘取数臂需要移动才能在 SQL Server 和 Windows 所要求的硬盘盘片的位置上进行查找和操作。如果数据所在的硬盘盘片的位置不连续,硬盘驱动器要花多得多的时间才能将磁盘取数臂和读/写磁头移动到所有需要的硬盘盘片位置。如果所需要的数据全部位于硬盘盘片上的连续物理扇区,情况则相反,磁盘取数臂和读/写磁头只需进行很小的移动就能完成所需磁盘 I/O 操作。连续和不连续的情况下所花的时间有很大的差异,每个不连续的数据查找大约要花 50 毫秒,而连续的数据查找则只需大约 2-3 毫秒。请注意这些值是粗略估计出来的,具体值将取决于不连续的数据在磁盘上分布的疏密、硬盘盘片的旋转速度 (RPM) 以及硬盘的其它物理属性。主要要记住的一点是连续 I/O 有益于 SQL Server 性能。 之前已提到标准的硬盘支持每秒 75 个不连续的 I/O 和每秒 150 个连续的 I/O。还要记住的重要一点是读或写 8KB 的时间与读或写 64 KB的时间几乎相同。在 8 KB 到 64 KB 范围之内,单个磁盘 I/O 传输操作所花的时间主要是磁盘取数臂和读/写磁头运动的时间。因此,从数学上来讲,当需要传输 64 KB 以上的 SQL 数据时,尽可能地执行 64 KB 磁盘传输是有益的,因为 64 KB 传输基本上与 8 KB 传输一样快,而每次传输的 SQL Server 数据是 8 KB 传输的 8 倍。请记住 Read-Ahead Manager 以 64 KB 字节片(也称为 SQL Server 扩展盘区)执行磁盘操作。Log Manager 也以较大的 I/O 传输量来执行连续写操作。要记住的主要事项是充分利用 Read-Ahead Manager,并将 SQL Server 日志文件与其它非连续存取的文件分开,以有效提高 SQL Server 的性能。
参考资料: Using NewSequentialID Instead of NewID NEWSEQUENTIALID() 重新组织和重新生成索引 序列化,反序列化时低序位非打印 ASCII 字符的问题最近碰到一个问题,我的一个把数据库中记录的信息暴露出来的Web Service调用时候出问题了。报下面的错误信息: System.InvalidOperationException was unhandled 当这个错误发生时,Web Service 服务器端不会有任何错误,而调用这个 Web Service 的客户端则会报上述错误。 <Value>在神奇天地裏誰叱咤風雨</Value> 会导致这些问题的 低序位非打印 ASCII 字符包含以下字符: 下面就是一个简单演示这个问题的控制台程序, using System; using System.Xml.Serialization; using System.IO; using System.Text; using System.Globalization; namespace TextSerialize { [Serializable] public class MyClass { public string Value { get; set; } } class Program { static void Main(string[] args) { string fileName = "d:\\1.txt"; MyClass c = new MyClass(); c.Value = string.Format("在神奇{0}天地裏誰叱咤風雨", Convert.ToChar(14)); SaveAsXML(c, fileName, Encoding.UTF8); object o = ConvertFileToObject(fileName, typeof(MyClass), Encoding.UTF8); MyClass d = o as MyClass; if (d != null) Console.WriteLine(d.Value); else Console.WriteLine("null"); Console.ReadLine(); } /// <summary> /// 序列化 /// </summary> /// <param name="objectToConvert"></param> /// <param name="path"></param> /// <param name="encoding"></param> public static void SaveAsXML(object objectToConvert, string path, Encoding encoding) { if (objectToConvert != null) { Type t = objectToConvert.GetType(); XmlSerializer ser = new XmlSerializer(t); using (StreamWriter writer = new StreamWriter(path, false, encoding)) { ser.Serialize(writer, objectToConvert); writer.Close(); } } } /// <summary> /// 反序列化 /// </summary> /// <param name="path"></param> /// <param name="objectType"></param> /// <param name="encoding"></param> /// <returns></returns> public static object ConvertFileToObject(string path, Type objectType, Encoding encoding) { object convertedObject = null; if (!string.IsNullOrEmpty(path)) { XmlSerializer ser = new XmlSerializer(objectType); using (StreamReader reader = new StreamReader(path, encoding)) { convertedObject = ser.Deserialize(reader); reader.Close(); } } return convertedObject; } } } 上面提到的Web Service 的那个问题,跟这个演示程序是一样的。 我们需要被序列化的内容中,存在 低序位非打印 ASCII 字符 时, .net 会给我们正常序列化, 会自动把 低序位非打印 ASCII 字符 转换成 &#x 编码的字符(这个XML规范中要求这么做的)。 但是,反序列化时候,如果需要反序列化的内容如果存在 &#x 编码的字符(映射到低序位非打印 ASCII 字符),则反序列化就会出错。
如果解决这个问题呢? 当然,最彻底的解决方法是修改反序列化的代码,让这些字符不会出错。但这个东西很多时候不归我们控制。这个方案不可行。 下一个方案就是剔除这些捣乱的字符。 我这里要给出的方案,是对这些字符序列化时作一次预处理,反序列化时,作一次反向处理。
/// <summary> /// 把一个字符串中的 低序位 ASCII 字符 替换成 &#x 字符 /// 转换 ASCII 0 - 8 -> � -  /// 转换 ASCII 11 - 12 ->  -  /// 转换 ASCII 14 - 31 ->  -  /// </summary> /// <param name="tmp"></param> /// <returns></returns> public static string ReplaceLowOrderASCIICharacters(string tmp) { StringBuilder info = new StringBuilder(); foreach (char cc in tmp) { int ss = (int)cc; if (((ss >= 0) && (ss <= 8)) || ((ss >= 11) && (ss <= 12)) || ((ss >= 14) && (ss <= 32))) info.AppendFormat("&#x{0:X};", ss); else info.Append(cc); } return info.ToString(); } /// <summary> /// 把一个字符串中的下列字符替换成 低序位 ASCII 字符 /// 转换 � -  -> ASCII 0 - 8 /// 转换  -  -> ASCII 11 - 12 /// 转换  -  -> ASCII 14 - 31 /// </summary> /// <param name="input"></param> /// <returns></returns> public static string GetLowOrderASCIICharacters(string input) { if (string.IsNullOrEmpty(input)) return string.Empty; int pos, startIndex = 0, len = input.Length; if (len <= 4) return input; StringBuilder result = new StringBuilder(); while ((pos = input.IndexOf("&#x", startIndex)) >= 0) { bool needReplace = false; string rOldV = string.Empty, rNewV = string.Empty; int le = (len - pos < 6) ? len - pos : 6; int p = input.IndexOf(";", pos, le); if (p >= 0) { rOldV = input.Substring(pos, p - pos + 1); // 计算 对应的低位字符 short ss; if (short.TryParse(rOldV.Substring(3, p - pos - 3), NumberStyles.AllowHexSpecifier, null, out ss)) { if (((ss >= 0) && (ss <= 8)) || ((ss >= 11) && (ss <= 12)) || ((ss >= 14) && (ss <= 32))) { needReplace = true; rNewV = Convert.ToChar(ss).ToString(); } } pos = p + 1; } else pos += le; string part = input.Substring(startIndex, pos - startIndex); if (needReplace) result.Append(part.Replace(rOldV, rNewV)); else result.Append(part); startIndex = pos; } result.Append(input.Substring(startIndex)); return result.ToString(); }
这样,我们这个演示程序的 Main 函数修改为下面的代码,也不会有任何错误发生。
static void Main(string[] args) { Console.WriteLine(GetLowOrderASCIICharacters("123456񐀀")); Console.WriteLine(GetLowOrderASCIICharacters("123456")); Console.WriteLine(GetLowOrderASCIICharacters("")); Console.WriteLine(GetLowOrderASCIICharacters("0123456789")); Console.WriteLine(GetLowOrderASCIICharacters("\f"));
小结 低序位非打印 ASCII 字符 在很多时候会给我们的系统带来问题,这部分字符必须作特殊处理。
参考资料: PRB: ErrorMessage 当 XML 文档包含低序位 ASCII 字符 如何解决用XmlSerializer序列化和反序列化类的过程中换行 \r 丢失? January 04 SQL Server 索引基础知识(4)----主键与聚集索引有些人可能对主键和聚集索引有所混淆,其实这两个是不同的概念,下面是一个简单的描述。不想看绕口文字者,直接看两者的对比表。尤其是最后一项的比较。 主键(PRIMARY KEY ) 来自MSDN的描述: 表通常具有包含唯一标识表中每一行的值的一列或一组列。这样的一列或多列称为表的主键 (PK),用于强制表的实体完整性。在创建或修改表时,您可以通过定义 PRIMARY KEY 约束来创建主键。 一个表只能有一个 PRIMARY KEY 约束,并且 PRIMARY KEY 约束中的列不能接受空值。由于 PRIMARY KEY 约束可保证数据的唯一性,因此经常对标识列定义这种约束。 如果为表指定了 PRIMARY KEY 约束,则 SQL Server 2005 数据库引擎 将通过为主键列创建唯一索引来强制数据的唯一性。当在查询中使用主键时,此索引还可用来对数据进行快速访问。因此,所选的主键必须遵守创建唯一索引的规则。
聚集索引 聚集索引基于数据行的键值在表内排序和存储这些数据行。每个表只能有一个聚集索引,因为数据行本身只能按一个顺序存储。 每个表几乎都对列定义聚集索引来实现下列功能:
两者的比较 下面是一个简单的比较表
参考: 下面这个帖子中大力的回复: SQL Server 索引基础知识(3)----测试中一些常看的指标和清除缓存的方法之前的两篇博客中有2个例子,来演示要讲述的内容。其中提到了部分查看数据库状态的方法,那里并不是很全面,这篇博客罗列几个我们在后面系列博客中会用到查看这些状态,数据的地方。以及测试中清除缓存的方法。 SQL Server 索引基础知识(1)--- 记录数据的基本格式 SQL Server 索引基础知识(2)----聚集索引,非聚集索引 如何获得索引的一些信息 比如:查看索引的深度SQL 脚本如下: select INDEXPROPERTY (OBJECT_ID('ChargeHeap'),'ChargeHeap_NCInd','IndexDepth') 更多属性请参看下面页面的参数说明: 或者我们在 SQL Server Management Studio 中选中我们要查看的索引,然后在右键菜单中查看索引的属性。其中 Fragmentation 标签页会有很多我们对 我们可以在这里看到索引的深度,子节点数,数据页数等等信息。这些信息对我们分析查询语句的性能非常有帮助。
如何查看磁盘I/O操作信息 SET STATISTICS IO ON 命令是一个 使 SQL Server 显示有关由 Transact-SQL 语句生成的磁盘活动量的信息。 我们在分析索引性能的时候,会非常有用。 启用了这个属性后,我们在执行 SQL 语句后,会收到类似如下的信息,这有利于我们分析SQL的性能: (3999 row(s) affected) 其中的 lob 逻辑读取、lob 物理读取、lob 预读 这三个指标是 读取 text、ntext、image 或大值类型 (varchar(max)、nvarchar(max)、varbinary(max)) 时的指标。 使用 SQL Server Management Studio Standard Reports 我们在 SQL Server Management Studio 中,选择数据库服务器,或者具体数据库,或者Security -- Logins 时,或者Management 时,Notification Services 或者 SQL Server Agent 对象时候,都会看到SQL Server 替我们提供的一些现成报表,这些报表的数据,有利于我们分析数据库的状态。 具体报表可以参考以下链接: 测试中,释放缓存的一些方法 尤其查询语句性能测试时,数据是否被缓存,这是测试中一个重要点。下面几个命令帮助我们清除缓存。方便测试。 清除缓存有关的命令: DBCC DROPCLEANBUFFERS DBCC FREEPROCCACHE DBCC FREESYSTEMCACHE 另外还可以 sp_cursor_list 查看全部游标 参考资料: January 02 SQL Server 索引基础知识(2)----聚集索引,非聚集索引由于需要给同事培训数据库的索引知识,就收集整理了这个系列的博客。发表在这里,也是对索引知识的一个总结回顾吧。通过总结,我发现自己以前很多很模糊的概念都清晰了很多。 不论是 聚集索引,还是非聚集索引,都是用B+树来实现的。我们在了解这两种索引之前,需要先了解B+树。如果你对B树不了解的话,建议参看以下几篇文章: B+ 树的结构图:
B+ 树的特点:
B+ 树中增加一个数据,或者删除一个数据,需要分多种情况处理,比较复杂,这里就不详述这个内容了。 聚集索引(Clustered Index)
下面是两副简单描述聚集索引的示意图: 在聚集索引中执行下面语句的的过程: select * from table where firstName = 'Ota' 一个比较抽象点的聚集索引图示:
非聚集索引 (Unclustered Index)
在非聚集索引中执行下面语句的的过程: select * from employee where lname = 'Green'
一个比较抽象点的非聚集索引图示:
什么是 Bookmark Lookup 虽然SQL 2005 中已经不在提 Bookmark Lookup 了(换汤不换药),但是我们的很多搜索都是用的这样的搜索过程,如下:
在 http://www.sqlskills.com/ 提供的一个例子中,就给我们演示了 Bookmark Lookup 比 Table Scan 慢的情况,例子的脚本如下: USE CREDIT go -- These samples use the Credit database. You can download and restore the -- credit database from here: -- http://www.sqlskills.com/resources/conferences/CreditBackup80.zip -- NOTE: This is a SQL Server 2000 backup and MANY examples will work on -- SQL Server 2000 in addition to SQL Server 2005. ------------------------------------------------------------------------------- -- (1) Create two tables which are copies of charge: ------------------------------------------------------------------------------- -- Create the HEAP SELECT * INTO ChargeHeap FROM Charge go -- Create the CL Table SELECT * INTO ChargeCL FROM Charge go CREATE CLUSTERED INDEX ChargeCL_CLInd ON ChargeCL (member_no, charge_no) go ------------------------------------------------------------------------------- -- (2) Add the same non-clustered indexes to BOTH of these tables: ------------------------------------------------------------------------------- -- Create the NC index on the HEAP CREATE INDEX ChargeHeap_NCInd ON ChargeHeap (Charge_no) go -- Create the NC index on the CL Table CREATE INDEX ChargeCL_NCInd ON ChargeCL (Charge_no) go ------------------------------------------------------------------------------- -- (3) Begin to query these tables and see what kind of access and I/O returns ------------------------------------------------------------------------------- -- Get ready for a bit of analysis: SET STATISTICS IO ON -- Turn Graphical Showplan ON (Ctrl+K) -- First, a point query (also, see how a bookmark lookup looks in 2005) SELECT * FROM ChargeHeap WHERE Charge_no = 12345 go SELECT * FROM ChargeCL WHERE Charge_no = 12345 go -- What if our query is less selective? -- 1000 is .0625% of our data... (1,600,000 million rows) SELECT * FROM ChargeHeap WHERE Charge_no < 1000 go SELECT * FROM ChargeCL WHERE Charge_no < 1000 go -- What if our query is less selective? -- 16000 is 1% of our data... (1,600,000 million rows) SELECT * FROM ChargeHeap WHERE Charge_no < 16000 go SELECT * FROM ChargeCL WHERE Charge_no < 16000 go ------------------------------------------------------------------------------- -- (4) What's the EXACT percentage where the bookmark lookup isn't worth it? ------------------------------------------------------------------------------- -- What happens here: Table Scan or Bookmark lookup? SELECT * FROM ChargeHeap WHERE Charge_no < 4000 go SELECT * FROM ChargeCL WHERE Charge_no < 4000 go -- What happens here: Table Scan or Bookmark lookup? SELECT * FROM ChargeHeap WHERE Charge_no < 3000 go SELECT * FROM ChargeCL WHERE Charge_no < 3000 go -- And - you can narrow it down by trying the middle ground: -- What happens here: Table Scan or Bookmark lookup? SELECT * FROM ChargeHeap WHERE Charge_no < 3500 go SELECT * FROM ChargeCL WHERE Charge_no < 3500 go -- And again: SELECT * FROM ChargeHeap WHERE Charge_no < 3250 go SELECT * FROM ChargeCL WHERE Charge_no < 3250 go -- And again: SELECT * FROM ChargeHeap WHERE Charge_no < 3375 go SELECT * FROM ChargeCL WHERE Charge_no < 3375 go -- Don't worry, I won't make you go through it all :) -- For the Heap Table (in THIS case), the cutoff is: 0.21% SELECT * FROM ChargeHeap WHERE Charge_no < 3383 go SELECT * FROM ChargeHeap WHERE Charge_no < 3384 go -- For the Clustered Table (in THIS case), the cut-off is: 0.21% SELECT * FROM ChargeCL WHERE Charge_no < 3438 SELECT * FROM ChargeCL WHERE Charge_no < 3439 go 这个例子也就是 吴家震 在Teched 2007 上的那个演示例子。 小结: 这篇博客只是简单的用几个图表来介绍索引的实现方法:B+数, 聚集索引,非聚集索引,Bookmark Lookup 的信息而已。 参考资料: 表组织和索引组织 How Indexes Work Bookmark Lookup Logical and Physical Operators Reference |
|
|