• 被一个貌似简单的老技术问题虐了几天


    公司的程序需要使用一套外部系统,是必须要用,没得商量的那种,这套外部系统Since 2001年,有将近18年的历史,跟它对接用什么方式?说出来你可能不信,竟然是用生成Microsoft Access(简称MSAccess)文件的方式,没错,就是生成mdb文件,往mdb里写数据,然后丢给这套外部系统。
     
    一个mdb,就是一个数据库,文件型数据库,这也是很多我这个年代来的程序员接触的第一个数据库系统,我记得最早的时候我是用VB(不是VB.NET,那时没有.NET)来访问它的。后来我发觉在比较简单的应用场景里,根本就不需要把数据保存到mdb中,直接读写带一定格式的文件比mdb访问快得多;再后来我发现可以用一些压缩算法来生成更小巧的数据存储文件;再后来我发现xml是一种可读性更好的数据存储方式;再后来我发觉sqlite功能蛮强,不比MSAccess差,由于开源跨平台,应用场景更丰富……总而言之,我没有理由再使用mdb来存储数据了。
     
    若干年后(如果今天看的话就是许多许多年前了),我去了一家公司,我所在的部门有一套非常重要的程序,用VC++写的,保存数据的文件的格式竟然就是mdb……VC++使用mdb文件的方式相当繁琐且容易出错,我当时觉得十分不爽,于是努力替换掉它,把它之前需要好几兆空间保存的数据压缩到几十K,你没看错,缩小了两个数量级,读写速度飞快,可这并不是一件令我有多少愉快回忆的事情,你想我这么一个新人努力地把人家辛辛苦苦定下来的东西“推倒重来”,人家会怎么想?
     
    又若干年后,命运又跟我开了个玩笑:对不起,虽然已经是2019年了,你还是得继续通过mdb文件交换数据!我:……
     

     
    现在公司的程序并非2019年才诞生,早有了,使用.NET Framework访问mdb文件比VC++轻松无数倍,虽然如此,我还是想进一步简化工作,我写了一套帮助类库,让程序无需关心具体操作,只需要准备好数据到一个数据字典中,把数据字典交给我的帮助类库,就自动完成了填充mdb文件的操作,非常方便。
     
    在刚过去的2018年里,我干了一件事情,就是将大量的程序从.NET Framework上迁移至.NET Core,这里边自然遇到了不少障碍,其中一个“无法逾越”的障碍就是mdb的生成!
     
    访问mdb数据库的正统方法是OLEDB接口,OLE这是个古老的概念,它基于COM(公共组件模型),COM的一个重要特点就是:Windows特有,并且,微软没有把它做成跨平台的打算。
     
    我花了很多时间寻找解决方案,未果,看来只能使用一些折中的做法:mdb的生成使用一个公共的Windows服务器来做,我自定义了一套通信规则,编写了生成mdb的服务程序,还提供了一套方便的客户端代码,调用者可以像原先那样,直接一个方法生成mdb,不用关心中间经过了那些网络通信步骤,还是一样的方便。我的程序经过了全面的测试,确认无误,一切看起来非常OK,直到系统正式使用的时候客户反映mdb的生成频频出问题……
     
    马上查看log,发现了这么一个错误:“XXXX正由另一进程使用,因此该进程无法访问此文件。”XXXX便是我要生成的mdb文件,我在生成mdb文件的时候,把模板复制到临时目录一份,名字用GUID,不会重复的,打开,填充数据,关闭,然后将mdb文件返回给客户端。这“正由另一进程使用”是什么意思?我的文件只可能是我这个进程使用啊。
     
    再经过了N次测试,我本地就是重现不出这个问题,只有把程序部署到服务器上才会出现,并且不能每次都重现,我的本地和服务器的差别是IIS Express和IIS的差别,但对IIS这么纷繁复杂的配置,我感觉要从中找出什么端倪来简直是大海捞针,搜索相关信息,无果。(不喜欢Windows服务器很大程度上是不喜欢IIS,对,我就这么旗帜鲜明)
     
    这个问题的本质就是:我关闭了OLEDB的连接后,mdb文件似乎还处于锁定状态,想用程序读取这个mdb文件返回的话就会出错。好,这个难不倒我,我开始寻找一些解决方法。 

    1,禁用连接池

     OLEDB数据库连接默认也是有连接池的,你关闭一个数据库连接,实际上并不是真的关闭它,而是让它返回到池中,准备给下次打开连接时使用。我很快找到了禁用连接池的选项,禁用之,测试,OK,上线!问题依旧。

    2,GC.Collect

     我虽然开始学.NET的时候就知道垃圾回收,可直接显式地使用这个方法,是第一次,按照微软官方文档的说法,一般情况下并无GC.Collect的必要,调用这个方法能强制释放一些内存资源,也许能强迫数据库真正关闭。但上线后很快发现问题依旧。

    3,外部复制

    经过尝试,我发觉竟然可以用Windows的copy命令来复制被打开着的mdb,我灵光一闪,对,服务器也可以这么干,于是关闭mdb连接后调用了外部的copy命令,复制生成的mdb文件,再把复制好的这个文件返回给客户端,perfect!果然,客户那边不再反映什么问题了。这个难题难道就这么解决了吗?图样!第二天,客户找到我们,说我们生成的mdb文件缺数据!这是大问题!甚至比生成不了mdb的问题还要打,前者的话他们可以再次尝试,而后者则可能带来业务上的数据错误。我马上到服务器上看,对比了生成的临时文件,我确认了问题还在!我这样copy被锁定的mdb文件是未写入完全的!有缓存,我要想方法禁掉缓存。

    4,尝试禁用OLEDB的写入缓存

    Windows在将数据存储至磁盘上的时候,其实都不是直接写磁盘,而是使用了一种缓存机制,先写到缓存,再缓存到物理磁盘,这样无疑提高了调用的速度,所以才有了Flush的概念,Flush就是立即将缓存的数据冲掉,也就是强制立即写入磁盘,而我现在要做的,是禁用掉这个缓存,让对mdb的操作更直截了当地写到磁盘上,我费了不少力气,在微软的官方文档上找到了这个选项,但是这回更惨,本地测试报错,原因是并不支持这个选项,可以理解为OLEDB只是接口,究竟支不支持,还得看引擎,访问mdb的这个Jet引擎貌似不行。

    5,更换引擎

    Microsoft.Jet.OLEDB.4.0变更为Microsoft.ACE.OLEDB.12.0,嗯,具体的安装文件可以在微软官网下载到。ACE.OLEDB.12.0这个应该更新一些,结果还是不行,并且一样地不支持上一点提到的那个选项。

    6,等待解锁

     既然上面都不是办法,那我就用重试的机制,读取文件失败,抛出异常,捕捉异常,间隔两秒钟,重试读取,再失败,再重试,最多尝试两分钟,两分钟文件总归写入完成了吧?结果还是不行,2分钟还是无法读取,且这回客户那边有些受不了这样的漫长等待了,客户问能不能恢复之前那个有数据缺失的版本?至少能用。

    7,到底有没有.NET Core下的访问mdb文件的第三方库

    微软不提供支持,我找第三方行不行?很显然,我之前找过,但这次我打算更认真仔细地找。最后找到了这么一篇文章:《Using Microsoft Access in .NET Core》,写于2018年11月,还挺新,难怪我之前没找到。文章提到了,.NET Core可以用ODBC接口访问mdb,但微软只提供了Windows版本的ODBC引擎,Linux版本的却没有提供,想在Linux环境下直接用ODBC访问mdb的话得找第三方的方案,这里就有一个:Access ODBC Driver,但并不免费,并且费用还不低。
     
    看吧,老板是肯定不会同意让我去买这么一个东西的。

    8,ODBC

     好,既然上面提到ODBC,那我就切换到ODBC接口去,不用什么OLEDB了,过程进展还挺顺利,除了对MSAccess的类型的理解有少许不同外,ODBC和OLEDB接口差别很小,我很快就弄完了。测试,上线!嗯?这次居然貌似可以了。客户用了一天下来,没再发现什么异常,我检查了日志,也没再出现“正由另一进程使用”的问题。这次难道真的好了吗?我已经有点焦头烂额了。
     
    好吧!至少到现在没再出什么问题。尽管我这几天里并不只在处理这个问题,但这个问题却是这几年来最让我掉面子的问题了。第一它很老,第二它看起来很简单,第三它似乎有很多种变通方法。但实际搞起来却被它虐了许多回合,实在令人唏嘘。
     

     
    半年前一个许多年前的老同事突然QQ我,说他在解决一个技术问题的时候搜到了我的博客,于是想起了我,挺怀念当初跟我学技术的日子,因为2018年了,他居然在做VB,没错,不是VB.NET,是VB,本文开头提到的那个VB,这是一套极其古老的系统,他还在维护着,我能理解他的苦闷,但这就是生活。
     
  • 相关阅读:
    UVA 1025 A Spy in the Metro DP水题
    ZOJ 3814 Sawtooth Puzzle BFS
    ZOJ 3816 Generalized Palindromic Number
    UVA 10859 Placing Lampposts 树形DP
    UVA 11825 Hackers' Crackdown 状压DP
    POJ 2887 Big String 线段树 离线处理
    POJ 1635 Subway tree systems Hash法判断有根树是否同构
    BZOJ 3110 k大数查询 & 树套树
    sdoi 2009 & 状态压缩
    来自于2016.2.24的flag
  • 原文地址:https://www.cnblogs.com/guogangj/p/10258345.html
Copyright © 2020-2023  润新知