背景:为什么需要一份核查清单
在用友体系的实施服务中,有一类项目的风险被系统性低估:存量客户交接。


典型场景是这样的------企业三到五年前由某家服务商上线了 T+ 或 U8,系统一直在跑,某天发现原服务商联系不上了,或者原服务商转做别的业务不再提供技术支持。企业此时只是"需要有人继续维护",看起来是个小需求,实际接手方拿到的是一个完全未知的黑盒:
- 数据库版本可能落后三到五个大版本,直接升级会失败
- 历史凭证里存在借贷不平、辅助核算缺失、负库存等逻辑错误
- 加密方式已变更,原加密狗在新版本上不认
- 原始实施文档全部缺失,科目体系和单据流转规则要靠反推
- 二次开发的存储过程、触发器、自定义报表没有任何说明
在这种情况下,直接谈迁移或升级是不负责任的,必须先做一次系统性的健康度核查。本文给出一份可执行的核查清单,覆盖环境层、结构层、数据层、性能层、安全层五个维度,并附检查用的 SQL 思路。
需要说明:用友各产品线的数据库结构随版本变化较大,文中表名与字段名以 U8 / T+ 常见版本为例,实际执行前请先用 sys.tables、sys.columns 确认当前账套的真实结构。
一、环境层核查
这一层回答的问题是:这套系统现在到底跑在什么上面。
1.1 数据库实例与版本
SELECT @@VERSION AS db_version;
SELECT SERVERPROPERTY('ProductVersion') AS product_version,
SERVERPROPERTY('Edition') AS edition,
SERVERPROPERTY('Collation') AS server_collation;
关注三点:
- SQL Server 版本。U8 早期版本对 SQL Server 版本有硬性要求,跨版本升级时(例如从 SQL Server 2008 R2 升到 2016 以上)需要先做兼容性验证,部分旧版本的兼容级别(compatibility_level)停留在 100 或更低,直接升级会触发执行计划退化。
- 排序规则 。用友账套对排序规则敏感,尤其是包含中文的
Chinese_PRC_CI_AS与Chinese_PRC_CS_AS(大小写敏感)混用会导致查询结果异常。跨实例迁移时必须保持一致。
- 实例名与端口。非默认实例、非 1433 端口在客户端连接配置中容易遗漏,交接时要在文档中记录。
1.2 各账套数据库的兼容级别与状态
SELECT name, compatibility_level, collation_name, state_desc, recovery_model_desc
FROM sys.databases
WHERE name LIKE 'UFDATA%' OR name LIKE 'UFTData%'
ORDER BY name;
U8 的账套库命名规则通常为 UFDATA_<账套号>_<年度>,T+ 系列通常为 UFTData<账套号>_<年度>(不同版本有差异)。这里要确认:
- 每个年度是否都有对应数据库(缺失年度库意味着历史数据不完整)
- 是否有数据库处于
RECOVERY_PENDING或SUSPECT状态
- 恢复模式是否为
SIMPLE(若是FULL但从未做过日志备份,日志文件会无限增长)
1.3 数据库文件与磁盘余量
SELECT DB_NAME(database_id) AS db_name,
name AS logical_name,
type_desc,
size * 8 / 1024 AS size_mb,
physical_name
FROM sys.master_files
WHERE DB_NAME(database_id) LIKE 'UFDATA%' OR DB_NAME(database_id) LIKE 'UFTData%'
ORDER BY size_mb DESC;
实践中经常遇到的情况:数据文件 2GB,日志文件 40GB。这不是性能问题,是备份策略缺失的表现。接手后第一件事是收缩日志并建立定期完整备份 + 日志备份的计划任务。
二、结构层核查
这一层回答:账套的注册关系是否完整、能否被系统正常识别。
2.1 系统库中的账套登记
U8 的账套信息登记在系统库 UFSystem 的 UA_Account(账套主表)与 UA_Account_sub(年度账信息)中。要检查的是账套登记与实际数据库是否一一对应:
USE UFSystem;
SELECT cAcc_Id, cAcc_Name, cAcc_Path, iYear, bYearClose
FROM UA_Account_sub
ORDER BY cAcc_Id, iYear;
常见异常:
UA_Account_sub中存在某年度记录,但对应的UFDATA_xxx_yyyy数据库已被删除或分离 → 登录时报"账套不存在"
- 数据库存在但系统库无登记 → 账套在列表中不可见,需要手工附加并补登记
- 账套路径
cAcc_Path指向的物理路径已变更(服务器迁移后未更新)→ 备份计划失效
2.2 存储过程与自定义对象
SELECT name, create_date, modify_date
FROM sys.objects
WHERE type IN ('P','FN','TF','TR','V')
AND is_ms_shipped = 0
ORDER BY modify_date DESC;
这一步的目的是识别二次开发遗留。原实施方做的报表、触发器、定时任务,往往没有任何文档。重点看三类:
- 触发器(TR):最容易造成隐蔽 bug。比如某张单据表上的触发器在插入时同步更新另一张表,一旦数据异常,排查成本极高。
- 作业(SQL Agent Job):是否有每天跑的同步、清理、计算任务。这些任务在原服务商离场后往往继续运行,但没人知道它们在做什么。
- 自定义函数(FN/TF):报表中大量引用,迁移时需要一并处理。
SELECT j.name, j.enabled, s.name AS schedule_name, s.freq_desc
FROM msdb.dbo.sysjobs j
LEFT JOIN msdb.dbo.sysjobschedules js ON js.job_id = j.job_id
LEFT JOIN msdb.dbo.sysschedules s ON s.schedule_id = js.schedule_id
WHERE j.name NOT LIKE 'sys%';
三、数据层核查
这是核查的核心。ERP 项目的"能用"和"用对"是两件事,数据层的错误往往在上线两三年后才暴露,而那时追溯成本极高。
3.1 凭证借贷平衡
U8 的凭证明细表通常为 GL_accvouch,科目汇总表为 GL_accsum。先做期间级的借贷平衡检查:
SELECT iPeriod,
SUM(md) AS debit_total,
SUM(mc) AS credit_total,
SUM(md) - SUM(mc) AS diff
FROM GL_accsum
WHERE iYear = 2024
GROUP BY iPeriod
HAVING ABS(SUM(md) - SUM(mc)) > 0.005;
返回任何一行都意味着该期间存在不平衡,必须定位到具体凭证。需要注意的是:金额字段为 money 或 decimal 类型时,浮点累计误差可能导致差额在 0.001 量级,判断阈值取 0.005 以上比较稳妥。
3.2 辅助核算完整性
辅助核算缺失是最常见的隐性错误。表现为:凭证录入了,但客户/供应商/部门/项目维度为空,导致辅助账与总账对不上。
SELECT COUNT(*) AS missing_aux_count
FROM GL_accvouch
WHERE cAuxType IS NOT NULL
AND cAuxType <> ''
AND (cCus_id IS NULL OR cCus_id = '');
这类问题的修复不能靠批量脚本,必须逐笔核对原始单据,因为缺失的辅助核算项无法从其他字段推导。
3.3 负库存检查
U8 的现存量表为 CurrentStock:
SELECT cWhCode, cInvCode, iQuantity
FROM CurrentStock
WHERE iQuantity < 0
ORDER BY iQuantity ASC;
负库存的成因通常有三类:出库单先于入库单录入、期初结存漏录、调拨单方向错误。需要说明的是,负库存不一定是数据错误------部分企业在实施时明确允许负库存出库以支持"先发货后补单"的业务实际。判断时要结合企业的业务规则,而不是一律修正。
3.4 往来账龄与挂账异常
SELECT cCusCode,
SUM(iAmount_f) AS balance,
MIN(dDate) AS earliest_date,
DATEDIFF(DAY, MIN(dDate), GETDATE()) AS aging_days
FROM (SELECT cCusCode, iAmount_f, dDate FROM GL_accvouch WHERE cCus_id IS NOT NULL) t
GROUP BY cCusCode
HAVING SUM(iAmount_f) <> 0
ORDER BY aging_days DESC;
超过 730 天仍有余额的往来挂账,要么是死账未清理,要么是核算错误。这类数据在迁移到新系统时会被完整继承,所以在迁移前清理比迁移后清理成本低得多。
3.5 单据断号与重复
单据号连续性在审计中是必查项。断号可能来自删除、也可能来自并发取号冲突。T+ 系列的单据号生成逻辑与 U8 不同,检查方式要按版本调整,通用思路是按单据类型统计最大号与实际记录数:
SELECT COUNT(*) AS cnt,
MIN(cCode) AS min_code,
MAX(cCode) AS max_code
FROM SA_SaleOrder
WHERE dDate >= '2024-01-01' AND dDate < '2025-01-01';
若 COUNT(*) 明显小于号段跨度,需要抽查确认是否存在非正常删除。
四、性能层核查
账套跑了三五年之后,性能问题开始显现,但多数企业会把"慢"归因为"软件不行",实际原因通常是索引碎片与统计信息。
4.1 索引碎片
SELECT OBJECT_NAME(i.object_id) AS table_name,
i.name AS index_name,
ips.avg_fragmentation_in_percent,
ips.page_count
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') ips
JOIN sys.indexes i ON i.object_id = ips.object_id AND i.index_id = ips.index_id
WHERE ips.avg_fragmentation_in_percent > 30
AND ips.page_count > 1000
ORDER BY ips.avg_fragmentation_in_percent DESC;
碎片率超过 30% 且页数超过 1000 的索引,应当重建(REBUILD);30% 以下可做重组(REORGANIZE)。这项维护在多数企业从未执行过。
4.2 大表与增长趋势
SELECT t.name AS table_name,
SUM(p.rows) AS row_count
FROM sys.tables t
JOIN sys.partitions p ON p.object_id = t.object_id AND p.index_id IN (0,1)
GROUP BY t.name
ORDER BY row_count DESC;
凭证明细、库存收发记录、日志表通常是前三名。如果某张日志类表行数超过业务表一个数量级,说明没有清理机制,需要单独制定归档策略。
4.3 阻塞与慢查询
SELECT TOP 20
qs.total_elapsed_time / qs.execution_count / 1000 AS avg_ms,
qs.execution_count,
SUBSTRING(qt.text, 1, 200) AS sql_snippet
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
WHERE qs.execution_count > 10
ORDER BY avg_ms DESC;
平均耗时超过 1 秒的高频语句,通常可以通过补索引或改写解决,这类优化对用户感知的提升最直接。
五、安全层核查
5.1 备份现状
SELECT database_name,
MAX(backup_finish_date) AS last_backup,
DATEDIFF(DAY, MAX(backup_finish_date), GETDATE()) AS days_ago,
backup_type
FROM msdb.dbo.backupset
GROUP BY database_name, backup_type
ORDER BY days_ago DESC;
三个判断:
- 最近一次完整备份距今超过 7 天 → 风险不可接受
- 从未做过事务日志备份 → 无法做时间点恢复,误操作只能回到上一次完整备份
- 备份文件与数据库在同一块物理磁盘 → 磁盘故障时同时丢失
5.2 恢复演练
没有做过恢复演练的备份,不能视为有效备份。 这一条在实践中被反复验证。操作方式:在测试实例上还原最近一次完整备份 + 日志备份,确认可以还原到指定时间点,并检查还原后的账套能否正常登录、报表能否打开。整个过程应记录耗时,作为故障恢复时间的参考。
5.3 授权与加密
U8 的授权信息登记在加密狗或软加密文件中,T+ 系列使用在线授权或本地授权文件。交接时要确认:
- 加密狗实物在谁手上(这是常被遗漏的资产)
- 软加密文件的备份位置与恢复方式
- 授权到期时间,以及续费主体是谁的名义
- 云产品(T+Cloud)的续费账号归属,是否仍在原服务商的控制下
最后一条尤其重要。SaaS 产品的账套归属权依赖账号,如果企业当初是以服务商名义开通的,交接不完成会出现"数据在、账号不在"的局面。
六、核查结论与迁移路径选择
核查完成后,根据结果选择三条路径之一:
|-----------------|-----------|------------------------|
| 数据健康度 | 版本落差 | 建议路径 |
| 良好(无平衡错误、无逻辑异常) | 1 个版本内 | 同版本迁移或原地升级 |
| 良好 | 跨 2 个以上版本 | 逐级升级,每一级做数据验证 |
| 存在逻辑错误 | 任意 | 先修复再升级,不可跳过 |
| 严重错误 + 版本过老 | 跨 3 个以上版本 | 评估重建:只迁移余额与期初,历史数据归档只读 |
第四种情况在实践中并不少见。判断标准是修复成本与重建成本的比较:如果历史数据需要逐笔核对超过两周的工作量,而企业只需要保留近三年的明细,那么"新账套 + 期初导入 + 旧账套归档查询"往往是更经济的方案。
七、交接文档清单
核查结束后应产出一份交接文档,最小内容包括:
- 服务器与数据库环境信息(版本、实例、端口、排序规则)
- 账套清单与年度对应关系
- 授权信息与到期时间
- 备份策略与最近一次恢复演练记录
- 二次开发清单(存储过程、触发器、作业、自定义报表)及用途说明
- 科目体系与单据流转规则说明
- 已知问题清单与处置建议
这份文档的价值在于:下一次交接时,接手的工程师不需要再从零反推。
一个实际案例
我们接手过一家县域煤矿企业的 T+Cloud 项目。原服务商停止运营后,企业的账套仍在云端运行,但出现了三个问题:库存数量与账面长期差异、月度结账需要手工调整分录、报表打开超时。
核查发现:基础物料编码存在重复(同名不同码、同码不同名两类),导致出入库汇总时数据被拆分;期初结存有部分物料漏录,后续通过调整单弥补,形成滚雪球式的差异;报表超时源于索引碎片率达到 62%。
处置顺序是:先做物料编码归并(这一步骤需要业务部门参与确认,耗时最长),再重算期初与历史差异,最后重建索引。整个过程约六周,其中五周在处理数据口径,一周在处理技术问题。
这个案例说明一件事:ERP 交接项目的技术难度通常不高,难度在数据治理,而数据治理必须由懂业务的人主导。 只懂数据库的工程师做不了这个工作,只懂业务的顾问也做不了。
本文由成都云策数链科技有限公司技术团队整理。深耕 T+Cloud、好业财、U8、U9 Cloud、YonSuite 等产品的实施与存量账套运维接续。文中 SQL 为检查思路示例,实际执行请以账套真实结构为准,操作前务必完成完整备份。