往Personal GDB或SDEGDB中写入要素记录是一件非常简单的事情,但似乎太多的情况下,写入数据的用例过于简单,使得许多代码问题、特别是ArcSDE本身的问题和Error无法暴露,很多人也从未意识到类似的问题。我最近正在做类似的工作,代码编写似乎不难,但测试就要了命了,发现的问题数不胜数,头痛不已。 1.插入记录的效率问题 向要素类中插入记录有两种方式,一是IFeature.Store,另一个是IFeatureCursor.Insert(IFeatureBuffer)和IFeatureCursor.Flush方法,显而易见的,后一种方法由于使用了缓存,速度上比前者快。 2.往MDB和SDE要素类中插入记录 这两种类型的要素类在往其中插入数据时不一定需要使用IWorkspaceEdit接口来开启和关闭一个Session,但是,如果SDE的要素类被注册为version,这个接口就必须使用了,否则CPU会高达100%,并会爆出“the operation in invalid on aclosed state”的错误。而使用了有版本的SDE要素类,我遇到了许多令人崩溃的SDEERROR,比如FDO_E_SE_DB_IO_ERROR、FDO_E_SE_OUT_OF_LOCKS等等。 3.将多个MDB导入一个MDB的时候,如果使用了IWorkspaceEdit接口,会出现某几个图层无法用ArcMap或ArcCatalog打开的情况,即以打开程序就崩溃,而不是用该接口,其它相同的代码做的加载过程,一切正常。 4.将两个或以上MDB导入一个MDB时候,到第二个MDB,很可能发生“**_SHAPE_INDEX被占用”的情况,这是因为写.NET平台代码时,你遍历或插入数据的游标cursor未释放的缘故。pFeatureCursor=null;并不会将对象从内存中清除,这是因为.NET平台是由Runtime来收集垃圾的,不像VC或VB那样能直接销毁COM对象,此时你应该使用System.Runtime.InteropServices.Marshal.ReleaseComObject方法来强制释放COM对象,以解除对某个表的独占状态。 关于释放SDE连接的问题 如果我们尝试往SDE中使用AO代码加载大量的数据,比如每个图层50万条记录,一共30个图层,那么这个过程是个不折不扣的噩梦,你会遇到许多匪夷所思的问题,一般而言,出了问题总是会报个fdoerr号,我们也可以查一查,但如果出现的问题号属于SDE ERROR类型,就不好说了,你会发现某些情况整个网络上都没有看到过,就是一个孤例。比如我遇到过的FDO_E_SE_DB_IO_ERROR和FDO_E_SE_OUT_OF_LOCKS等。 我在往SDE中写代码导入数据时候频繁遇到FDO_E_SE_DB_IO_ERROR问题,尽管safe网站上给出了该问题的三种可能原因,但我一直都没能从Oracle的角度解决。统计一下往SDE导入数据成功的情况,每次导入一个要素类,即每个要素类导入时都给开启一个专用的gsrvr进程,成功率是最高的,几乎为95%。 不成功的10%是一次导入200万条标注要素类,但分开岛,每次50万条,也一切安好。 那么问题就来了,我们知道,SDE安装后,SDE与DBMS之间有一个giomgr进程负责管理双方的通讯过程,即根据访问请求建立gsrvr进程。一般地,gsrvr将处理与SDE有关的查询,存储,删除等操作,并且它能处理多个连接请求,它与DBMS的关系就是“通则不痛,通则不痛”,一旦传输的数据量多了,gsrvr就开始抽风了。我们的问题是,如何让一个要素类处理完成后,释放这个连接,即关闭该gsrvr进程,在下一个要素类开始时再次建立一个新gsrvr进程。这个方式的确也是最为保险的。 现在问题就是,如何释放SDE连接,事实上,如果使用纯COM对象来写,这几乎不是问题,有人认为将工作空间对象设置为null即可。比如我们写个VB的代码: dim pFWS as IFeatureWorkspace set pFWS=SdeWorkspaceFactory.open() '1 set pFWS=nothing '2 去看看Oracle的连接,在1执行完后,应该有两个SDE链接,而在执行完2以后,就只有一个SDE链接了,其所有者是giomgr。显然,SDE连接被关闭了。 那么在.NET平台中,这种setpFWS=nothing是行不通的,我们得使用System.Runtime.InteropServices.Marshal.ReleaseComObject(pFWS);才能让非托管代码在内存中被清除。试一试,也可以发现SDE的连接关闭。 将问题再复杂一点,如果我们遍历一次pFWS中的某个要素类,会发现即使使用System.Runtime.InteropServices.Marshal.ReleaseComObject(pFWS);也搞不定,解决的方法是,将你在代码中new出来的所有的AO对象都用System.Runtime.InteropServices.Marshal.ReleaseComObject方法释放,某些循环产生的对象,如: pFeature=pFeatureCursor.NextFeature(); while(pFeature!=null) { System.Runtime.InteropServices.Marshal.ReleaseComObject(pFeature); pFeature=pFeatureCursor.NextFeature(); } System.Runtime.InteropServices.Marshal.ReleaseComObject(pFeatureCursor); 也必须销毁,如果有一个与SDE数据库相关的对象未销毁,则仍然存在两个SDE连接。 使用这种一个图层一个gsrvr进程的方法后,再未出现过FDO_E_SE_DB_IO_ERROR问题。 关于Network I/O Error的错误 好了,继续我最近关于操作SDE海量数据库的心得。我开发的一个功能是将MDB中的要素写入SDE要素类中,这个功能并不复杂,我早就写过一个非常完备的LoadFeatureClass函数,但在加载要素之前,我还需要在SDE库中进行一些操作,也就是对每遍历一个MDB中的要素类时,必须对SDE库中对应的两个要素类进行一些处理,结果,就非常“幸运”地频繁遇到了Network I/O Error[xxx(某个要素类)]。 根据异常的提示,似乎SDE与Oracle之间发生了“不愉快”的事情,即链接断了,但这个可能性马上被我否定了,要知道我在初步测试用的是本机,不可能发生这种情况。那第二个原因就是gsrvr.exe这个进程爆掉了,导致出现了这种异常假象,果然不出我的预测,在每次出现这个异常后,Oracle的会话中只有一个SDE会话(如果连接上SDE,标准应该有两个SDE会话,一个是SDE监听会话,另一个是链接会话)。 gsrvr.exe这个链接进程为什么会爆掉?我在网上看了一些相关的文档,列举了七七八八的解决之道:
删除MDB中几个要素类看看?果然,在删除了4个要素类后,频繁出现的Network I/O Error问题居然消失了。 综合前面发生的许多异常和这个问题,我最后得出了一个推论:一个SDE进程gsrvr.exe一次只能在SDE库中产生有限个数的要素游标,并且在每个游标上能够传输的数据是有限的,否则gsrvr.exe就会爆掉。这两个因素是同时存在的,因为一个gsrvr.exe遍历SDE多个要素类不会产生问题,只产生一个要素游标却传输上百万条数据也不会产生问题。 有鉴于此,最后的解决方法仍然是遍历了14-18个要素类后就强制关闭SDE链接,然后再产生SDE链接继续操作。虽然牺牲了时间,但却取得了可靠性。 |