数据库通讯
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, MD108 | 1s | 高频关键数据 |
| 状态组 | MX0.0, MW200, MW202 | 5s | 中频状态数据 |
| 统计组 | MD500, MD504 | 60s | 低频统计数据 |
自动建表
系统自动创建数据表, 表结构如下:
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 统一接入多种主流数据库:
| 引擎 | 底层库 | 典型场景 | 连接字符串示例 |
|---|---|---|---|
| SQLite | System.Data.SQLite | 本地配方库, 单机部署 | Data Source=C:\Darra\plc.db;Cache=Shared |
| SQL Server | Microsoft.Data.SqlClient | 上位配方下发系统的读库 | Server=.\\SQLEXPRESS;Database=RecipeDB;Trusted_Connection=True; |
| MySQL | MySql.Data | 配方/工单只读视图 | Server=192.168.1.10;Database=recipe;Uid=plcuser;Pwd=***; |
| PostgreSQL | Npgsql | 配方 + 时序混合 | Host=pg-server;Database=recipe;Username=plcuser;Password=*** |
| TimescaleDB | Npgsql + TS 扩展 | 设备变量归档 (Historian) | 同 PostgreSQL |
| ClickHouse | ClickHouse.Client | 设备变量的海量历史 OLAP | Host=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 类型 |
|---|---|---|
| BindBool | BOOL | BIT / BOOLEAN |
| BindInt | INT / DINT | INT / BIGINT |
| BindReal | REAL / LREAL | FLOAT / DOUBLE / DECIMAL |
| BindString | STRING | VARCHAR / NVARCHAR |
| BindDateTime | DT / DATE_AND_TIME | DATETIME / TIMESTAMP |
| BindBytes | ARRAY OF BYTE | VARBINARY / 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 配置)
安全建议
- 最小权限账号: 为 PLC 创建专用账号, 只给必要表的
SELECT/INSERT/UPDATE, 禁止 DDL - 只读账号分离: 报表只读 + PLC 写入 分两个账号
- 参数绑定: 严禁字符串拼接 SQL (FB 禁用了原始文本模式)
- 加密连接: 使用 TLS, SSL Mode = Required
- 密码存储: IDE 加密后存
connections.xml, 下载时 Runtime 侧再解密 - 审计: 开启数据库侧审计日志, 留痕关键表操作
典型应用 (合理的频率范围)
| 用途 | 表结构 | 写入频率 | 方式 |
|---|---|---|---|
| 班次汇总 | 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 存储 |
相关文档: