跳到主要内容

数据库通讯

Darra PLC 支持多种关系数据库集成, 默认 SQLite, 可扩展到 SQL Server/MySQL/PostgreSQL 等。

核心定位: 以读为主, 写罕见

PLC 不是业务系统。数据库的典型用法是 "从数据库读配方/工单/BOM 等设备参数进 PLC", 写回仅限于班次结束汇总、故障归档、审计日志等低频场景。

严禁把 PLC 当业务数据库的写频道使用 (例如在扫描循环里高频 INSERT 订单/物料/库存), 高频写会直接拖慢扫描周期, 破坏实时控制。高频变量归档走异步管道, 见 异步数据管道

功能概述

特性说明
数据库引擎SQLite (默认) / SQL Server / MySQL / PostgreSQL / TimescaleDB
SQLite 日志模式WAL (读写并发不阻塞)
主要用途读取配方/工单/BOM 下发 PLC
次要用途班次结束汇总、审计日志、报警归档
高频归档通过异步管道 → Historian (时序库), 不走这里

典型应用场景

读场景 (主)

  • 配方加载: 换型时 SELECT * FROM recipes WHERE code=? 批量写 PLC DB 块
  • 工单下发: 从上位系统导出的工单只读视图, PLC 读取当前任务号/数量
  • BOM 读取: 装配清单提供给 PLC 按步执行
  • 权限表: HMI 登录读取用户-角色关系

写场景 (罕见)

  • 班次结束汇总: 每班一行 (时间, 产量, 废品, OEE)
  • 故障归档: 报警复位时一次性写一条
  • 审计日志: 关键参数修改 (who, when, old, new)
  • 配方版本追溯: 换型时一行 (operator, recipe_code, ts)

配置

{
"protocol": "SQLite",
"databasePath": "C:\\Darra\\Data\\plc_data.db",
"walMode": true,
"pollIntervalMs": 1000,
"maxRecords": 10000000,
"autoArchiveDays": 90
}

数据记录

记录组配置

将 PLC 变量按组配置, 每组独立的记录周期:

记录组变量周期说明
温度组MD100, MD104, MD1081s高频关键数据
状态组MX0.0, MW200, MW2025s中频状态数据
统计组MD500, MD50460s低频统计数据

自动建表

系统自动创建数据表, 表结构如下:

CREATE TABLE IF NOT EXISTS plc_log_temperature (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT NOT NULL DEFAULT (datetime('now', 'localtime')),
MD100 REAL,
MD104 REAL,
MD108 REAL
);

CREATE INDEX idx_temperature_ts ON plc_log_temperature(timestamp);

数据查询

通过 REST API 或 SDK 查询历史数据:

// 查询最近 1 小时的温度数据
var adapter = PLCDriverFactory.Create(ProtocolId.SQLite);
await adapter.ConnectAsync(new ConnectionConfig
{
DatabasePath = @"C:\Darra\Data\plc_data.db"
});

var result = await adapter.QueryAsync(
"SELECT timestamp, MD100 FROM plc_log_temperature " +
"WHERE timestamp > datetime('now', '-1 hour') " +
"ORDER BY timestamp"
);

高性能模式优势

高性能模式

数据库高性能模式允许读操作和写操作并发执行, 不互相阻塞:

  • 写入: PLC 周期性写入不影响查询性能
  • 读取: 历史数据查询不阻塞实时记录
  • 检查点: 自动执行数据检查点

数据归档

超过保留期限的数据自动归档为压缩文件:

C:\Darra\Data\
├── plc_data.db 当前数据库
├── plc_data.db-wal WAL 日志
├── archive/
│ ├── 2026-01.db.gz 1月归档
│ ├── 2026-02.db.gz 2月归档
│ └── 2026-03.db.gz 3月归档

支持的数据库引擎

除 SQLite 外, Darra PLC 通过 ADO.NET 统一接入多种主流数据库:

引擎底层库典型场景连接字符串示例
SQLiteSystem.Data.SQLite本地配方库, 单机部署Data Source=C:\Darra\plc.db;Cache=Shared
SQL ServerMicrosoft.Data.SqlClient上位配方下发系统的读库Server=.\\SQLEXPRESS;Database=RecipeDB;Trusted_Connection=True;
MySQLMySql.Data配方/工单只读视图Server=192.168.1.10;Database=recipe;Uid=plcuser;Pwd=***;
PostgreSQLNpgsql配方 + 时序混合Host=pg-server;Database=recipe;Username=plcuser;Password=***
TimescaleDBNpgsql + TS 扩展设备变量归档 (Historian)同 PostgreSQL
ClickHouseClickHouse.Client设备变量的海量历史 OLAPHost=ch;Port=8123;Database=historian

引擎在工程里用下拉选择, 连接字符串参数根据引擎动态渲染。所有引擎共用同一套 FB 接口, 程序里不需要区分。

连接配置

在 IDE 中配置数据库连接:

项目树 → 通讯 → 右键 → 添加 → 数据库连接

界面项:

字段说明
连接名工程内唯一, 例如 db_recipe
引擎上表之一
Host/Port服务器地址和端口
数据库名目标 Database / Schema
认证方式用户密码 / Windows 集成 / 证书
用户名登录名
密码加密保存, 下载时重新加密到 Runtime
连接池最大默认 10
连接超时默认 15s
命令超时默认 30s
SSL 模式Required / Preferred / Disabled

配置文件写入 comm/connections.xml:

<Connection name="db_recipe" type="SQLServer">
<Host>recipe-sql.example.com</Host>
<Port>1433</Port>
<Database>RecipeDB</Database>
<Auth>User</Auth>
<Username>plc_read</Username>
<Password encrypted="true">...</Password>
<Pool max="10"/>
<Timeout connect="15" command="30"/>
</Connection>

数据库 FB 功能块

PLC 程序通过标准 FB 访问数据库, 无需直接写 ADO.NET 代码:

DB_CONNECT — 建立/检查连接

VAR
Connect : DB_CONNECT;
END_VAR

Connect(
ConnectionName := 'db_recipe',
Execute := TRUE,
Timeout := T#15s
);

IF Connect.Done THEN
// 连接可用
ELSIF Connect.Error THEN
// Connect.ErrorCode / Connect.ErrorMessage
END_IF

DB_SELECT — 查询

VAR
Select : DB_SELECT;
Rows : ARRAY[0..99] OF DB_ROW;
Count : INT;
END_VAR

Select(
ConnectionName := 'db_recipe',
SQL := 'SELECT id, code, speed FROM recipes WHERE active = 1',
Execute := TRUE,
ResultArray := Rows,
MaxRows := 100
);

IF Select.Done THEN
Count := Select.RowCount;
// Rows[0].Cell[0].AsInt = id
// Rows[0].Cell[1].AsString = code
// Rows[0].Cell[2].AsReal = speed
END_IF

DB_INSERT — 插入 (低频, 班次汇总 / 审计)

写入使用限制

INSERT 仅用于低频场景: 班次结束汇总、故障归档、审计日志。 严禁每扫描周期写 — 会阻塞 PLC 扫描线程。 高频变量归档走 异步数据管道

VAR
Insert : DB_INSERT;
END_VAR

// 触发条件: 班次结束时 (1 班 1 次, 非每周期)
Insert(
ConnectionName := 'db_recipe',
SQL := 'INSERT INTO shift_summary(ts, station, qty, ok) VALUES(:ts, :st, :q, :ok)',
Execute := ShiftEnd
);
Insert.BindDateTime('ts', NOW());
Insert.BindString('st', 'Station01');
Insert.BindInt('q', ShiftTotalQty);
Insert.BindBool('ok', ShiftResult);

IF Insert.Done THEN
// 插入行数 = Insert.AffectedRows
// 自增 ID = Insert.LastInsertId
END_IF

DB_UPDATE — 更新 (仅罕见配方回写)

VAR
Upd : DB_UPDATE;
END_VAR

// 罕见: 手动修正配方参数后回写, 不在 PLC 扫描链触发, 由 HMI 操作驱动
Upd(
ConnectionName := 'db_recipe',
SQL := 'UPDATE recipes SET speed = :s WHERE id = :id',
Execute := ManualTrigger
);
Upd.BindReal('s', NewSpeed);
Upd.BindInt('id', RecipeId);

DB_DELETE — 删除 (仅归档清理)

// 每日一次, 清理已确认的老报警
Delete(
ConnectionName := 'db_recipe',
SQL := 'DELETE FROM alarms_archive WHERE ack = 1 AND ts < :cut',
Execute := DailyTrigger
);
Delete.BindDateTime('cut', NOW() - T#30d);

DB_EXECUTE — 存储过程 (归档类任务)

Exec(
ConnectionName := 'db_recipe',
SQL := 'EXEC sp_ArchiveOldLogs @days = :d',
Execute := WeeklyTrigger
);
Exec.BindInt('d', 90);

DB_TRANSACTION — 事务包裹

// 场景: 换型时写一行切换审计 + 更新配方活动状态 (整个过程一班仅 1~2 次)
Txn(ConnectionName := 'db_recipe', Begin := TRUE);
Insert(...); // 写 changeover_log
Update(...); // 更新 station_recipe
IF Insert.Error OR Update.Error THEN
Txn(Rollback := TRUE);
ELSE
Txn(Commit := TRUE);
END_IF

参数绑定

所有 FB 都强制使用参数绑定 (:name 占位), 运行时由驱动转义, 杜绝 SQL 注入:

绑定方法PLC 类型SQL 类型
BindBoolBOOLBIT / BOOLEAN
BindIntINT / DINTINT / BIGINT
BindRealREAL / LREALFLOAT / DOUBLE / DECIMAL
BindStringSTRINGVARCHAR / NVARCHAR
BindDateTimeDT / DATE_AND_TIMEDATETIME / TIMESTAMP
BindBytesARRAY OF BYTEVARBINARY / BLOB
BindNull(任意)NULL

连接池

每个数据库连接配置维护一个连接池:

参数默认说明
最小连接数0空闲时保留的连接
最大连接数10并发 FB 调用上限
空闲超时5 min超时后关闭连接
探活间隔60s周期 SELECT 1
重连延迟2s → 4s → 8s → 16s → 30s指数退避

所有 FB 调用在取连接时阻塞等待 (上限 = 命令超时)。池满时后续调用返回 Error = TRUE, ErrorCode = POOL_EXHAUSTED

事务

事务作用域为单个连接。并发使用时:

  • 同一时刻, 一条连接只能参与一个事务
  • 嵌套事务不支持, 内层 begin 视为 savepoint (如果引擎支持)
  • 隔离级别: READ_COMMITTED 默认, 可通过 FB 的 Isolation 参数改
Txn(
ConnectionName := 'db_recipe',
Begin := TRUE,
Isolation := ISOLATION_SERIALIZABLE
);

错误处理与重试

FB 的错误码分两类:

可重试错误 (自动重试 3 次)

  • TIMEOUT — 单次超时
  • CONNECTION_LOST — 连接断开
  • DEADLOCK_DETECTED — 数据库死锁
  • SERVER_UNAVAILABLE — 服务器短暂不可达

不可重试错误 (立即上报)

  • SYNTAX_ERROR — SQL 语法错误
  • AUTH_FAILED — 认证失败
  • CONSTRAINT_VIOLATION — 主键/外键/唯一约束冲突
  • INVALID_PARAMETER — 参数绑定错误
  • PERMISSION_DENIED — 权限不足

重试次数和间隔可在连接配置里自定义。重试失败后 FB 置 Error = TRUE, PLC 程序决定如何降级。

批量操作

批量 = 汇总, 不是高频

批量不等于高频。批量 FB 用于"一次性写 N 行汇总", 例如班次结束一次写入 1 小时的每分钟产量快照。 仍然严禁在扫描周期内反复调用 DB_BATCH_INSERT。 高频变量归档必须走 异步数据管道, 经 RingBuffer + 异步 Writer 线程, 不走 PLC 同步 FB。

DB_BATCH_INSERT

VAR
Batch : DB_BATCH_INSERT;
Rows : ARRAY[0..999] OF DB_ROW;
END_VAR

// 场景: 班次结束时一次写入 1 小时节拍汇总
Batch(
ConnectionName := 'db_recipe',
TableName := 'shift_cycle_summary',
Columns := 'ts,station,qty,ok',
Rows := Rows,
RowCount := N,
Execute := ShiftEnd
);

引擎层面:

  • SQL Server: 用 SqlBulkCopy, 单次 1000 行约 50 ms
  • PostgreSQL: 用 COPY FROM STDIN, 同等性能
  • MySQL: 用多值 INSERT
  • SQLite: 用 Transaction + Prepared

数据采集场景 (仅低频)

IDE 提供 "数据采集" 配置视图, 无需写代码即可把选定的 PLC 变量低频写入关系型数据库。

低频采集限制

此配置仅适用于秒级以上的采集 (>= 1s), 作为业务统计层的补充。 高频 (毫秒级) 变量归档必须用 异步数据管道 的 Historian 方案, 不要用此机制, 否则会拖垮扫描。

项目树 → 数据服务 → 采集任务 → 新建

设置项:

字段说明
任务名工程内唯一
连接选择已配的数据库连接
表名目标表 (不存在可自动建)
采集周期1 s 起步 (低于此值请走 Historian) / 10 s / 60 s / 事件驱动
变量列表从变量表勾选, 自动对应列
触发条件连续 / 变化时 / 外部触发
失败策略丢弃 / 缓存重试 / 写本地落盘
时间戳列自动 ts

采集任务背后编译为一个隐藏的 FB, 与项目一起下载到 Runtime。

报表与查询

IDE 内置报表编辑器, 基于查询模板生成表格/图表:

项目树 → 报表 → 新建 → 从数据库查询
  • 选连接 → 写 SQL → 点预览
  • 拖字段到图表 (柱/线/饼)
  • 导出为 Excel / PDF / HTML
  • 定时生成, 邮件推送 (需 SMTP 配置)

安全建议

  1. 最小权限账号: 为 PLC 创建专用账号, 只给必要表的 SELECT/INSERT/UPDATE, 禁止 DDL
  2. 只读账号分离: 报表只读 + PLC 写入 分两个账号
  3. 参数绑定: 严禁字符串拼接 SQL (FB 禁用了原始文本模式)
  4. 加密连接: 使用 TLS, SSL Mode = Required
  5. 密码存储: IDE 加密后存 connections.xml, 下载时 Runtime 侧再解密
  6. 审计: 开启数据库侧审计日志, 留痕关键表操作

典型应用 (合理的频率范围)

用途表结构写入频率方式
班次汇总ts, line, station, qty, ok每班 1 行DB_INSERT
换型审计ts, station, from_code, to_code, operator每次换型 1 行DB_INSERT
报警归档id, ts, code, level, msg, acked事件触发DB_INSERT
配方读取id, code, params...每次换型读一次DB_SELECT
工单读取order_no, product, qty_target每次接单读DB_SELECT
权限读取user, role登录时一次DB_SELECT
能耗统计date, meter, kwh 汇总每日/小时DB_INSERT (聚合后)
高频变量归档

不在此表 — 所有毫秒级/秒级的设备变量持续采样都走 Historian, 异步管道不阻塞扫描。

排错

症状原因处理
连接超时防火墙 / 路由不通Telnet 测试 host:port
认证失败密码过期 / 账号被锁数据库侧确认账号状态
插入失败 "数值超范围"PLC REAL → SQL INT检查列类型
唯一约束冲突主键重复改用 UPSERT / ON CONFLICT
查询慢无索引执行 EXPLAIN, 加索引
并发死锁多 FB 操作同一行序列化写入 / 缩小事务范围
日期时区错服务器/PLC 时区不同统一 UTC 存储

相关文档: