4. 数据库接口与工程数据管理

在工程实践中,试验数据、标定参数、设备台账、测试记录往往并不存放在单个 MAT 文件里,而是集中在 MySQL、PostgreSQL、SQL Server、Oracle 或 SQLite 等关系型数据库中。MATLAB 的 Database Toolbox 正是为这类场景设计的:它让 MATLAB 直接成为数据库的客户端,用 SQL 完成查询、写入、更新和删除,再把结果以 table、数值矩阵或时间表的形式带回工作区继续做算法处理。本节围绕“连接—执行 SQL—批量读写—事务处理”这条主线展开,重点讲清楚每一步实际发生了什么、参数如何设置、结果如何判断。

一、建立连接:从数据源到数据库对象

连接数据库有两种典型方式。第一种是使用 ODBC/JDBC 驱动配合 database 函数,适合本地或内网数据库;第二种是使用 mysql、postgresql、sqlite、sqlserver 等原生接口函数,配置更简洁。以 MySQL 为例,典型写法如下:

conn = mysql('localhost', 'engineer', 'Matlab@2024', ...
             'DatabaseName', 'test_bench', 'PortNumber', 3306);
% 检查连接是否可用
if isopen(conn)
    disp('数据库连接成功');
end

这里 localhost 是主机名,engineer 是账号,第三个参数是密码,DatabaseName 指定默认库,PortNumber 指定端口。连接成功后返回的是一个数据库连接对象,后续所有 SQL 操作都基于它。需要特别注意的是:连接对象持有底层资源,使用完毕必须用 close(conn) 关闭,否则连接池会被占满,后续连接可能失败。若使用 JDBC,还需要先用 javaclasspath 把驱动 jar 加入 Java 类路径,并确认驱动版本与数据库服务端版本兼容。

二、执行 SQL:查询、写入与参数化

查询用 fetch,写入用 exec,这是最常用的两个入口。查询示例:

sql = ['SELECT test_id, torque, speed, temp, ts ' ...
       'FROM bench_record WHERE ts >= ''2024-03-01'' ' ...
       'AND temp > 85 ORDER BY ts'];
data = fetch(conn, sql);
head(data, 5)

fetch 默认把结果集读成 table,列名来自 SQL 的字段名,数值列自动映射为 double,时间列映射为 datetime。若结果集很大,应避免一次性全量拉取,而是用 WHERE 加时间范围或主键范围分批读取。写入示例:

insertSQL = ['INSERT INTO bench_record ' ...
             '(test_id, torque, speed, temp, ts) ' ...
             'VALUES (?, ?, ?, ?, ?)'];
exec(conn, insertSQL, {1001, 210.5, 3200, 92.3, '2024-03-05 10:12:00'});

这里用 ? 占位符配合元胞数组传参,是参数化查询的标准做法。它有两个好处:一是避免字符串拼接导致的引号转义错误,二是防止 SQL 注入。工程数据里常出现单引号、中文、特殊符号,参数化能显著降低出错概率。执行后应检查 conn.Message 是否为空,或查看 conn.AutoCommit 状态,确认语句真正生效。

三、批量读写:把 table 高效送进数据库

逐行 exec 在数据量大时非常慢,因为每次都是一次网络往返。Database Toolbox 提供了 sqlwrite 和 sqlread 两个高层函数,直接以 table 为输入输出,内部使用批量提交,效率明显更高:

% 批量写入:data 为 table,列名与表字段一致
sqlwrite(conn, 'bench_record', data);

% 批量读取:按条件取回
opts = databaseImportOptions(conn, 'bench_record');
opts.RowFilter = 'temp > 85 AND speed BETWEEN 3000 AND 3500';
subset = sqlread(conn, 'bench_record', opts);

批量写入前要保证 table 的变量名与目标表字段名一致,类型也要匹配:double 对应 FLOAT/DOUBLE,datetime 对应 DATETIME/TIMESTAMP,string 或 cellstr 对应 VARCHAR/TEXT。若字段类型不匹配,sqlwrite 会报类型转换错误,此时应先用 convertvars 调整列类型。批量读取时,RowFilter 会被拼进 SQL 的 WHERE 子句,因此不要在其中放入不可信的外部输入。

四、事务处理:保证一批操作要么全成、要么全不成

工程数据写入经常是“主表 + 明细表”成组出现,例如一条试验记录对应多条测点数据。如果主表写入成功而明细表失败,数据库就会处于不一致状态。事务正是解决这个问题的机制。默认情况下连接处于自动提交模式,每条语句独立生效;要使用事务,需要先关闭自动提交:

conn.AutoCommit = 'off';
try
    exec(conn, 'INSERT INTO test_main (test_id, operator, ts) VALUES (?, ?, ?)', ...
         {2001, 'Zhang', '2024-03-06 09:00:00'});
    sqlwrite(conn, 'test_detail', detailTable);
    commit(conn);          % 两步都成功,统一提交
catch ME
    rollback(conn);        % 任一步失败,全部回滚
    rethrow(ME);
end
conn.AutoCommit = 'on';

这段代码的关键在于:commit 之前的所有修改对其他会话不可见,只有提交后才持久化;一旦 rollback,本次事务内的所有写入都会被撤销。判断事务是否成功,不能只看有没有抛异常,还要确认 commit 本身没有报错。对于跨库或跨表的复杂写入,建议把事务边界控制在一次业务操作内,避免长事务锁表影响其他用户。

Database Toolbox 工程数据管理链路 MATLAB 工作区 table / timetable double / datetime 试验数据、标定参数 连接对象 conn mysql / postgresql sqlite / sqlserver AutoCommit 状态 关系型数据库 test_main 主表 test_detail 明细表 bench_record 记录表 database() SQL 通道 SQL 操作入口 fetch 查询 exec 单条写入 sqlwrite 批量写 sqlread 批量读 事务控制 事务处理:AutoCommit = 'off' 写主表 test_main exec(...) 写明细 test_detail sqlwrite(...) commit / rollback 全成提交,失败回滚 连接 → SQL 执行 → 批量读写 → 事务提交/回滚,构成工程数据管理的完整闭环

五、工程数据管理中的几个实用判断

第一,连接失败时先看错误码:驱动未加载、账号密码错误、端口不通、库名不存在,这四类原因占绝大多数。第二,查询结果为空不一定是 SQL 写错,也可能是时区或时间格式不一致,建议统一用 ISO 格式字符串传时间。第三,批量写入前先用 sqlread 读回表结构,核对字段名和类型,比事后排错更省时间。第四,事务中不要执行耗时过长的查询,否则会长时间持有锁。第五,敏感信息如密码不要硬编码在脚本里,可放入环境变量或 MATLAB 的 setSecret 机制中读取。

建议把“连接参数、表结构、常用 SQL、事务边界”整理成一份工程数据接口说明,随项目一起维护。这样换人接手或迁移数据库时,只需改连接配置,业务逻辑不必重写。

把上述流程串起来,MATLAB 就不再只是算法工具,而是能直接参与工程数据全生命周期管理的客户端:从数据库取数、在内存中计算、把结果批量写回、用事务保证一致性。掌握连接、SQL、批量读写和事务这四个环节,就具备了用 Database Toolbox 支撑实际工程项目的数据接口能力。