

就是拉明细账的时候,要同时关联凭证、科目余额,数据量稍微大一点,页面转半天都出不来。
我之前接了个外包,帮一家小微企业改旧财务系统,客户就提了一个需求,打开余额表到查对应明细账,必须两秒内出来。
当时打开原系统试了一下,3年的凭证数据不到10万条,查询居然花了17秒,直接给我整懵了。

看了原来的源码设计差点没背过气,三个核心表根本没做关系优化,查询的时候全表扫描三次,再做三次关联,能不慢吗?
做财务开发的都知道,科目余额表、凭证表、明细账表是整个财务系统的核心三张表,所有的对账、出表、查账全靠这三张,关联查询搞不定,整个系统体验直接垮掉。


很多人刚做财务开发,会觉得这不就是三个独立的表吗?凭证明细存在凭证表,科目余额存在余额表,分开存不就好了?
真用起来你就知道不对,查明细账的时候,你得知道对应凭证的字号、制单人、审核时间吧?你得对应当前科目的累计余额吧?你做跨年查询的时候,还要把上年结转的余额带出来吧?
这些数据分散在三张表里,不关联根本拿不到能用的结果。
科目余额表存的是每个会计期间,每个科目的期初余额、本期发生额、期末余额,按会计期间和科目编码做聚合。
凭证主表存的是每张凭证的基础信息,比如凭证号、凭证日期、制单信息,凭证明细表其实就是我这里说的明细账表,每一行就是一条具体的分录,对应科目、借方发生额、贷方发生额。
正常的关联逻辑是,明细账表通过凭证ID关联凭证主表拿到凭证信息,再通过科目编码+会计期间关联科目余额表拿到当前期间的余额数据。
原来的设计就是直接写SELECT * FROM 明细账 LEFT JOIN 凭证 ON 明细账.凭证ID=凭证.ID LEFT JOIN 科目余额 ON 明细账.科目编码=科目余额.科目编码 AND 明细账.期间=科目余额.期间,小数据量看不出问题,数据超过10万条直接卡死。


我有个做了十年财务开发的老哥,跟我聊起这个事说,90%的性能问题,本质上都是索引设计的问题,剩下10%是冗余设计没做好。
先说说索引,很多人知道给关联字段加索引,但就是加不对地方。
比如明细账表最常用的查询条件就是科目编码和会计期间,很多人只给科目编码加了单索引,实际上应该给(会计期间, 科目编码, 凭证ID)加联合索引,查询的时候直接覆盖索引拿数据,根本不用回表。
凭证表的主键本身就是ID,这个没问题,但如果你经常按凭证日期查询,最好给凭证日期加个普通索引,筛选的时候快很多。
科目余额表最关键的就是(会计期间, 科目编码)联合唯一索引,因为本身每个期间每个科目就一条数据,加了唯一索引,关联的时候直接定位到那一条,不用全表扫。
你别小看索引调整,我当时把原来错的索引删掉,重新建了联合索引,查询时间直接从17秒降到了5秒,这第一步提升就已经很夸张了。

然后就是反常规的冗余设计,很多人说数据库设计要符合三范式,尽量不要冗余,我告诉你做财务系统,三范式要适当打破。
你想想,每一条明细账都要对应一个科目,每次关联都要拿科目余额表的期初数,能不能把期初数直接冗余到明细账查询的结果层面?不对,应该是直接把科目编码和期间冗余本来就有,那换个思路,把每一张凭证对应的分录,提前预聚合到凭证主表?也不对,真正好用的冗余是,把科目余额表的期末余额,提前按期间和科目算好存着,查询的时候不用实时聚合本期发生额。
原来很多旧系统的设计是,科目余额表不存数据,每次查询都拿明细账的发生额加总去算余额,我的天呐,那能不慢吗?
现在标准的优化方式就是,每次做完月末结账,直接把每个科目的余额算好写到科目余额表里,查询的时候直接读,不用实时算,这一下子又砍掉了一大半的计算时间。
还有一个点,很多人想不到,就是关联查询的时候不要都用左连接。
你想想,合法的明细账,对应的凭证一定是存在的,对应的科目余额一定是存在的,那直接用内连接不好吗?左连接会让数据库保留不必要的空行,查询效率本来就比内连接低,只要数据完整性做了约束,直接用内连接,速度还能再提一点。


我当时改完,拿真实数据测了一下,客户五年的凭证数据,总共快50万条明细账,查询某一个科目一整年的明细账,同时带出凭证信息和每一行的余额,你猜多少秒出来?
不到1.2秒!直接把客户看呆了,原来等半天才能出的数据,现在点一下直接出来。
这里还有一个小技巧,就是分页查询的优化,很多人分页是先关联三张表,拿到所有结果再分页,那肯定慢啊。
正确的做法是,先在明细账表,根据你筛选的条件(比如期间、科目编码)先查出符合条件的分页ID,拿着这些ID再去关联另外两张表拿数据,这样每次只关联当前页十几二十条数据,速度瞬间上去了。
我记得之前看到过一个国内做开源财务系统的评测,说他们优化完三张表的关联查询,百万级数据下,查询响应时间都能控制在3秒以内,其实就是用了上面说的这些方法,没什么特别黑科技,就是细节做到位了。
还有人会问,那我用云数据库,配置高一点是不是不用优化了?不对,配置再高,你写法不对,该卡还是卡,我见过用8核16G的云服务器,跑几万条数据还卡的,就是因为表设计烂,索引乱加。

其实做财务系统的源码开发,最考验功夫的就是这些核心表的设计,别的功能做的花里胡哨,一查账就卡,用户直接就不用了。
我个人觉得,很多人做开发追求新框架、新技术,反而忘了最基础的数据库设计优化,其实对业务系统来说,核心表的结构设计对了,整个系统的性能就成功了一大半。
这个问题我也不敢说把所有优化点都说到了,不同的业务场景还有不同的调整方式,比如有的公司需要做多辅助核算,那索引还要再调整,不过核心思路都是一样的,就是让关联的时候尽量少扫描数据,尽量提前把能算的数算好存起来,不要每次查询都临时计算。
你要是正在做财务系统的开发,或者刚好要优化查询速度,不妨对着这几张表看看,是不是索引加错了,是不是还在实时计算余额,改完肯定能吓你一跳,速度提升真的太明显了。

夜雨聆风