☰
基于MySQL的工厂生产与库存管理系统:表设计、并发控制与运维实战
2026/9/26 5:53:51 网站建设 项目流程

1. 库存账对不上那天,我决定把亮片厂的生产账搬进MySQL

车间主任把一叠纸质领料单拍在办公桌上:“这批B457亮片明明还剩500公斤库存,系统里却只显示330公斤,你们做的基于mysql的亮片厂生产及库存管理系统到底行不行?”这一幕发生在我们给一家亮片厂做信息化改造的第三个月。其实问题不在MySQL本身,而在最初的表结构和库存扣减逻辑太粗糙——但这恰恰是很多工厂管理系统做崩的起点。

亮片厂这个行业有个特点:SKU爆炸。同一款亮片按直径分为2mm、3mm、5mm,按颜色分为银色、金色、幻彩、激光,按材质又分PET、PVC、金属感镀膜,再叠加批次号和生产日期,物料种类轻松上千种。过去用Excel管,车间开单、仓管记账、财务统计各搞一套,月底一核对,差异不是几十公斤,而是几百公斤。更要命的是,亮片生产工艺里有大量染色、压膜、切割工序,半成品和成品混在同一个仓库,批次之间互相覆盖,追溯起来全靠老师傅的记忆。

我当时的判断是,与其上一套重型ERP让工人天天填表单,不如先用MySQL做一套贴合亮片行业生产节拍和库存特性的轻量系统。MySQL能扛住这种规模的数据量,又是开源方案,部署灵活,维护成本低,跟着工厂从单机用到多车间联网都不会出现明显瓶颈。后面这几个月里,我们顺手把库存不准、领料超发、报表卡死、备份丢数据这些坑一个个填掉,积累下来的经验可能对其他小制造企业也有参考价值。

2. 从工单到批次库存:先设计好数据表,再谈系统功能

很多项目一上来就写业务代码,等到库存逻辑跑不通了才回头改表结构,这是最大的坑。MySQL再强,也救不了一个没有主键、没有索引、到处是重复字段的混乱库。在亮片厂这个场景里,我建议把数据模型拆成四张核心表:物料主数据、生产工单、批次库存、出入库流水。每张表解决一个明确的业务问题,表与表之间通过工单号、批次号关联。

2.1 物料主数据:色号、规格、材质决定SKU粒度

亮片厂的物料编码不是随便编个数字就完事,要考虑仓库摆放和后续报表统计。我们最终定的编码规则是“材质码-直径-色号-表面处理”,比如PET-5-GS101-幻彩,对应一张5mm银色幻彩PET亮片。数据库里的物料表我留了这样几个字段:

CREATE TABLE dim_material ( material_code VARCHAR(32) NOT NULL COMMENT '物料编码', material_name VARCHAR(64) NOT NULL COMMENT '物料名称', size_mm DECIMAL(5,2) NOT NULL COMMENT '直径(mm)', color_no VARCHAR(16) NOT NULL COMMENT '色号', surface_type VARCHAR(32) DEFAULT NULL COMMENT '表面处理类型', material_type VARCHAR(16) NOT NULL COMMENT '材质:PET/PVC/金属', unit VARCHAR(8) DEFAULT 'kg' COMMENT '计量单位', status TINYINT DEFAULT 1 COMMENT '1启用 0停用', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (material_code), KEY idx_size_color (size_mm, color_no) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='亮片物料主数据';

这里有几个细节值得说明。第一,字符集我统一用utf8mb4而不是utf8,因为色号里经常有特殊符号和生僻字,utf8mb4对emoji和四字节字符也能兼容,避免入库时报错。第二,联合索引idx_size_color不是随便建的,后面做“按规格查库存、按色号统计产量”这种高频查询时,这个索引能直接覆盖过滤条件。第三,status字段非常重要,停产物料不能直接删除,否则历史工单关联会断,只能逻辑停用。

2.2 生产工单与工序流转记录:每一道工序的下落都要能查

亮片的生产流程大致是:原料切片、染色、压膜、切割、筛选、包装。多数工厂关心两个数字——这批单子计划做多少、实际完成多少。工单表把这两个数字作为核心,再用工序流转表记录每一道工序的完工数量、操作人和时间。

CREATE TABLE prod_work_order ( work_order_no VARCHAR(32) NOT NULL COMMENT '工单号,如WO20250618001', material_code VARCHAR(32) NOT NULL COMMENT '生产物料编码', planned_qty DECIMAL(10,2) NOT NULL COMMENT '计划生产数量(kg)', finished_qty DECIMAL(10,2) DEFAULT 0 COMMENT '累计完工数量(kg)', status TINYINT DEFAULT 0 COMMENT '0待开工 1生产中 2已完工 3已冻结', plan_start_date DATE DEFAULT NULL, plan_end_date DATE DEFAULT NULL, actual_end_date DATETIME DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (work_order_no), KEY idx_wo_material (material_code), KEY idx_wo_status (status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='生产工单';

工序流转记录则单独建一张明细表,每次报工就是插入一条记录,不会去反复UPDATE工单表。因为车间工人报工频率很高,如果每次都改工单主表,行锁竞争会非常严重。把报工做成“只追加”的流水表,汇总完工数量时用SUM,这个设计在后续并发场景里帮我们省了很大麻烦。

2.3 批次库存:按批追溯是亮片厂的红线

库存表是整个系统的核心。亮片厂的库存不能简单按物料编码汇总,比如同样是PET-5-GS101,不同批次的染色深浅有差异,客户对色差要求高的必须指定批次出货。所以库存表必须带batch_no,一张表同时管库存和追溯。

CREATE TABLE inv_batch ( id BIGINT AUTO_INCREMENT PRIMARY KEY, batch_no VARCHAR(32) NOT NULL COMMENT '批次号', material_code VARCHAR(32) NOT NULL COMMENT '物料编码', warehouse VARCHAR(16) NOT NULL COMMENT '仓库编号', qty_available DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '可用库存(kg)', qty_frozen DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '冻结库存(kg)', qty_total DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '账上总库存(kg)', production_date DATE DEFAULT NULL COMMENT '生产日期', supplier_batch VARCHAR(32) DEFAULT NULL COMMENT '原料供应商批次', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_batch (batch_no), KEY idx_batch_material (material_code, warehouse) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='批次库存';

与之配套的出入库流水表不做“修改”,只做“追加”,每次业务动作都写一条记录,包含操作前数量、变动数量、操作后数量,这样一旦库存对不上,顺着流水就能反查是谁在哪个环节搞错了。表结构如下:

CREATE TABLE inv_stock_record ( id BIGINT AUTO_INCREMENT PRIMARY KEY, batch_no VARCHAR(32) NOT NULL, record_type VARCHAR(8) NOT NULL COMMENT 'IN入库/OUT出库/FREEZE冻结/UNFREEZE解冻', order_no VARCHAR(32) DEFAULT NULL COMMENT '关联工单号或销售单号', change_qty DECIMAL(10,2) NOT NULL, before_qty DECIMAL(10,2) NOT NULL, after_qty DECIMAL(10,2) NOT NULL, operator VARCHAR(32) NOT NULL, remark VARCHAR(255) DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_record_batch (batch_no), KEY idx_record_time (create_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='出入库流水';

这套模型的好处是:库存表只管数值,所有“这个数是怎么来的”都交给流水表去解释。MySQL的InnoDB引擎本身就支持事务,任何一笔出入库都是在同一个事务里同时更新inv_batch和写入inv_stock_record,两条语句要么都成功、要么都失败,这纸面账就永远有据可查。

3. 并发领料扣库存:事务、锁与一次死锁排查全记录

表结构设计好之后,我们很快遇到了第二个坎:车间多个班组同时领料,明明库存够,扣完之后负数了。打开数据库一看,PHP脚本里用的是“先SELECT查库存,判断够不够,再UPDATE扣减”,三段式操作在单用户时没问题,多用户同时提交时,两个请求都读到剩余300公斤,各自判断可以扣200公斤,最后库存变成-100公斤。

3.1 为什么直接UPDATE会出现超发

问题根源不在MySQL,而在读取和写入之间留下了时间窗口。MySQL默认的存储引擎InnoDB在可重复读隔离级别下,普通的SELECT是快照读,不会锁记录,两个事务可以同时读到同一个旧值。要解决超发,方案有两个:

方案一是在UPDATE语句里加条件判断,让扣减操作本身具备原子性:

UPDATE inv_batch SET qty_available = qty_available - 200 WHERE batch_no = 'B45720250618' AND qty_available >= 200;

这条语句执行后返回影响行数。如果返回1说明扣减成功,返回0说明库存已经不够。MySQL在更新时会自动对命中的记录加行锁,所以两个并发事务同时执行这条UPDATE,后执行的那个会被阻塞,等前一个提交后再判断条件,这时候qty_available已经是扣完后的值,条件不满足就返回影响行数为0。整个判断和扣减在数据库内部完成,业务代码不需要再“先查再改”。

方案二是用SELECT ... FOR UPDATE手动锁行:

START TRANSACTION; SELECT qty_available FROM inv_batch WHERE batch_no = 'B45720250618' FOR UPDATE; -- 在业务代码里判断查询结果 UPDATE inv_batch SET qty_available = qty_available - 200 WHERE batch_no = 'B45720250618'; COMMIT;

FOR UPDATE加的是排他锁,事务提交前其他事务的SELECT ... FOR UPDATE必须等待。这个方案更灵活,可以先把一整批的多个物料锁定再统一处理,但代价是持锁时间长、锁冲突概率大。就亮片厂领料这种高频小事务,我推荐方案一,单条UPDATE语句最省事,性能也最好。

3.2 锁定批次记录的两种方案对比

在给车间演示方案的时候,我顺手整理了一张对比表,放在项目文档里给其他同事参考:

方案实现方式锁粒度适合场景风险点
条件UPDATEUPDATE ... SET qty = qty - ? WHERE qty >= ?行锁,仅锁命中记录单表库存扣减、高频领料复杂业务无法在一个语句内完成
SELECT FOR UPDATE先锁行再处理业务行锁,持续到事务结束需要读多行后统一判断的场景持锁长,容易形成锁等待甚至死锁
乐观锁版本号UPDATE ... SET version = version + 1 WHERE version = ?无数据库锁,靠版本号控制更新频率很低的配置类数据冲突时需重试,不适合高并发扣减

对亮片厂来说,95%的库存操作是“同一批次扣一个数”,用条件UPDATE就够了。剩下5%的复杂场景,比如多个批次按先进先出规则凑单出库,才需要事务里读取多个批次再用条件UPDATE逐个扣减。

3.3 死锁发生后的排查链路

上线第二周,车间反馈系统突然卡住,页面一直转圈,MySQL CPU飙升。我第一时间执行了这条命令:

SHOW FULL PROCESSLIST;

结果发现两个事务都处于“Waiting for lock”状态,互相在等对方释放锁。典型的死锁场景——事务A先锁批次1再锁批次2,事务B先锁批次2再锁批次1。MySQL的InnoDB引擎默认会检测死锁,并自动回滚其中代价较小的事务,所以理论上不应该永久卡住,但当时的问题是事务里还夹杂了其他表的操作,死锁检测触发后业务代码没做重试,直接抛异常给用户弹了个错误页。

排查死锁原因时,最有用的工具是这条:

SHOW ENGINE INNODB STATUS \G

在输出内容里找到“LATEST DETECTED DEADLOCK”段落,里面会明确显示两个事务分别持有哪些锁、等待哪些锁、执行的SQL是什么。我们那次死锁的根因是出库程序先从工单表查信息,再按物料顺序锁库存表;而另一个入库程序先从库存表锁批次,再回头更新工单表,两者顺序不一致。

解法很简单:在代码层面统一锁定顺序——所有事务都以“先批号后工单”的固定顺序操作数据库对象。同时,把涉及多个批次的出库逻辑改成按batch_no排序后再逐个锁定,从根上消除循环等待的可能。这个改动后,死锁再没出现过。

4. 把业务规则下沉到数据库:存储过程与触发器的实际用法

系统跑起来之后,车间反馈操作太繁琐——每次入库要在两个界面分别填:填库存数量、填流水原因,少填一项就报错。我决定把“入库自动生成批次、自动写流水”这套规则直接做进MySQL里,用触发器和存储过程让数据库自己扛业务规则。

4.1 入库触发器:自动生成批次号并写库存流水

亮片的批次号规则是“物料编码前4位+生产日期+流水号”,例如PET5-20250618-001。手工生成容易重复,使用者的输入顺序也不统一,干脆由触发器自动处理。我在inv_batch表上建了一个BEFORE INSERT触发器:

DELIMITER // CREATE TRIGGER trg_batch_before_insert BEFORE INSERT ON inv_batch FOR EACH ROW BEGIN DECLARE seq INT DEFAULT 0; SELECT COUNT(*) + 1 INTO seq FROM inv_batch WHERE DATE(create_time) = CURDATE(); SET NEW.batch_no = CONCAT( 'B', DATE_FORMAT(NOW(), '%Y%m%d'), '-', LPAD(seq, 3, '0') ); END// DELIMITER ;

注意两点。第一,MySQL客户端和存储过程之间有一条默认的分隔符冲突:SQL语句本身用分号结尾,而触发器体内也有分号,如果不先把客户端的结束符临时改成DELIMITER //,MySQL会误以为触发体在某条分号处已经结束,导致语法报错。这是新手写存储过程和触发器时最常见的坑。第二,COUNT(*) + 1这种方式在极端并发下可能生成重复序号,如果批次号要求绝对唯一,更稳妥的写法是利用表的自增主键或者UUID去拼接,我们实际生产环境里最终改成了“日期+自增ID”,彻底避免并发重复。

4.2 领料存储过程:一次性完成校验、扣减、记账

入库用触发器解决之后,出库我改造成存储过程receipt_out,把“校验批次、扣减库存、冻结记录、写流水”四步全部包在一个事务里。核心逻辑如下:

DELIMITER // CREATE PROCEDURE sp_receipt_out( IN p_batch_no VARCHAR(32), IN p_qty DECIMAL(10,2), IN p_order_no VARCHAR(32), IN p_operator VARCHAR(32) ) BEGIN DECLARE v_available DECIMAL(10,2); START TRANSACTION; SELECT qty_available INTO v_available FROM inv_batch WHERE batch_no = p_batch_no FOR UPDATE; IF v_available < p_qty THEN ROLLBACK; SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '库存不足,出库失败'; ELSE UPDATE inv_batch SET qty_available = qty_available - p_qty, qty_frozen = qty_frozen + p_qty WHERE batch_no = p_batch_no; INSERT INTO inv_stock_record ( batch_no, record_type, order_no, change_qty, before_qty, after_qty, operator ) VALUES ( p_batch_no, 'OUT', p_order_no, -p_qty, v_available, v_available - p_qty, p_operator ); COMMIT; END IF; END// DELIMITER ;

这里有个业务设计上的讲究:出库先扣可用库存、增加冻结库存,等司机装车确认出库单后再真正减少冻结库存。为什么这样设计?因为亮片厂经常出现“领料单开了,但车没来拉、货还在仓库”的情况,如果直接把库存扣掉,月底盘库会莫名出现差异;用冻结库存过渡,既保证账上不能超发,又为后续“取消出库、商品退回”留了操作余地。

把业务规则写进存储过程还有一个好处:无论前端是Web页面、扫码枪小程序还是Excel导入,最终都走同一个存储过程,不会出现某个入口忘了写流水、某个入口忘了校验库存的情况。代码逻辑集中在一个地方,审计和维护都方便。不过提醒一下,存储过程也别滥用,过度复杂的业务逻辑会让你后期排错很痛苦。我给自己定的标准是:涉及数据完整性的事务性操作(扣库存、记账)才用存储过程,纯查询和统计坚决不用。

4.3 触发器在数据同步与导入场景里的用途

触发器还有一个非常实用的场景是“数据变更留痕”。亮片厂经常用Excel批量导入库存期初数,如果手工操作很容易覆盖正常数据。我在关键表上加了AFTER UPDATE触发器,把每次改动前的旧值自动保存到一张history表里。这样即便某个人误操作把库存清零了,管理员也能从history表里快速恢复,不用去看binlog。

但触发器也有副作用:它无法在事务里看到“其他连接未提交”的数据,多个触发器嵌套触发时容易让执行计划变得不透明。生产环境里我的建议是能用应用层逻辑就别用触发器凑数,触发器适合做“轻量级、无争议”的自动补充,比如生成批次号、写审计日志,复杂业务校验放存储过程或服务端代码里更可控。

5. 报表越跑越慢:索引与慢查询优化记录

系统上线三个月,数据量到了一定规模,新的问题又出现了——月底生产报表和库存日报越来越慢,财务每次统计当月各色号亮片的产量要等一分多钟。业务方说“系统卡死了”,其实不是卡死,是写了大量全表扫描的SQL。

5.1 先定位慢在哪:从一张会全表扫描的报表SQL说起

最初的产量报表SQL长这样:

SELECT material_code, DATE_FORMAT(actual_end_date, '%Y-%m') AS month, SUM(finished_qty) AS total_qty FROM prod_work_order WHERE actual_end_date >= '2025-01-01' AND actual_end_date < '2025-07-01' AND status = 2 GROUP BY material_code, DATE_FORMAT(actual_end_date, '%Y-%m');

数据量到了十几万行以后,这个查询要扫描全表,因为WHERE条件里的actual_end_date上没有索引,GROUP BY用到的material_code和格式化后的日期也无法命中索引。MySQL的查询优化器只能从头到尾扫一遍prod_work_order表,再把结果在内存里做临时表排序和聚合,自然慢。

执行EXPLAIN确认:

EXPLAIN SELECT ... -- 可以看到 type=ALL,rows=160000

type=ALL就是全表扫描,rows显示扫描了16万行。对这种统计类SQL,第一反应不是加内存、换机器,而是看能不能让索引顶上去。

5.2 加联合索引之后:从12秒降到0.2秒

优化方案分两步。第一步,给prod_work_order表加一个覆盖统计条件的联合索引:

ALTER TABLE prod_work_order ADD INDEX idx_status_date (status, actual_end_date, material_code);

为什么是(status, actual_end_date, material_code)这个顺序?因为WHERE条件里status是等值判断,actual_end_date是范围判断,material_code用于分组和后续排序。MySQL索引最左前缀原则决定了要把等值条件的列放在最前,范围条件的列放中间,最后再接需要覆盖的字段。这样查询时先用status='2'定位到已完工工单的子集,再按actual_end_date范围过滤,GROUP BY material_code的时候直接按索引顺序分组,连临时表排序都省了。

但原SQL里还有DATE_FORMAT(actual_end_date, '%Y-%m'),这个函数包裹让索引没法继续高效用于分组。所以优化SQL时我干脆调整写法,按月分组改成直接对日期列做范围切分:

SELECT material_code, DATE_FORMAT(actual_end_date, '%Y-%m') AS month, SUM(finished_qty) AS total_qty FROM prod_work_order WHERE status = 2 AND actual_end_date >= '2025-01-01' AND actual_end_date < '2025-07-01' AND material_code IN ('PET5-GS101', 'PET3-GS102', ...) GROUP BY material_code, month;

如果业务报表只关心某几个热卖色号,还可以在WHERE里显式传入material_code列表,也就是把原本对全表的统计缩小到几个索引branch上,速度会进一步大幅提升。加索引后同一张报表从平均12秒降到了0.2秒,效果非常直观。

5.3 分页排序里的隐藏陷阱:深翻页和隐式类型转换

除了报表,仓库的扫码出库页面也存在隐患。扫码枪按“先进先出”规则翻批次库存列表,最初用的是:

SELECT batch_no, qty_available FROM inv_batch WHERE warehouse = 'A01' ORDER BY production_date ASC LIMIT 10 OFFSET 20000;

当offset很大的时候,MySQL即使走了索引,也要先把前20010行扫出来再丢掉前20000行,效率越来越低。如果你的分页页面会翻到几十页之后,建议改用“游标式”分页,记住上一页最后一条记录的位置,用WHERE条件代替OFFSET:

SELECT batch_no, qty_available FROM inv_batch WHERE warehouse = 'A01' AND (production_date, id) > ('2025-06-01', 123456) ORDER BY production_date, id LIMIT 10;

这种写法能始终命中索引、只取10行,是深分页场景的标准解法。

还有一个容易被忽略的坑是隐式类型转换。MySQL有一条规矩:字符串和数字比较时,如果字段是字符串类型,MySQL会把字符串转成数字再比较,此时索引直接失效。有一次库存查询传参失误,把batch_no写成了整数,SQL立刻从索引查询退化成全表扫描。排查时EXPLAIN里type从ref变成了ALL,再看WHERE条件才发现批量参数的类型没对。写查询条件时,参数类型和字段类型保持一致,这个小习惯能省掉大量半夜排查索引失效的时间。

6. 上线后的运维生存指南:备份、安装、连接问题的实战处理

系统上线不是终点,运维才是真正考验人的地方。亮片厂没有专职DBA,维护这套MySQL的人可能就是厂里的IT或者软件供应商的技术支持,所以我把最常见的几类问题都提前设了预案。

6.1 备份策略:mysqldump加binlog双保险

最开始我只用mysqldump每天凌晨做一次全量备份,结果某天上午误删了一张库存表,恢复到凌晨的备份意味着当天上午的所有出入库流水全部丢失。这个教训让我加了binlog策略。MySQL开启binlog后,每次数据变更都会记录到二进制日志,配合全量备份可以实现任意时间点的恢复。

备份命令示例:

mysqldump -u root -p --single-transaction --master-data=2 --all-databases > /backup/full_$(date +%F).sql

--single-transaction的意思是备份InnoDB表时基于事务快照,不锁表,这样白天备份也不影响车间正常领料。--master-data=2会在备份文件里记录当时binlog的位置,恢复时从这个位置往后重放binlog,就能把误操作前的数据找回来。

6.2 生产环境MySQL部署的安装细节

部署MySQL时,亮片厂这种规模我通常推荐直接装Linux服务器或用Docker起一个容器,比在Windows上装更稳。这里提几个容易踩的坑。

一是关闭MySQL默认的大小写敏感问题。Linux下MySQL默认表名区分大小写,Windows下不区分,两边的库迁移过来往往会因为大小写不一致报找不到表。稳妥做法是在my.cnf里统一加上:

[mysqld] lower_case_table_names=1

二是字符集和排序规则在初始化阶段就要定好。Docker安装MySQL时,最好在run命令里直接指定:

docker run -d \ --name mysql-prod \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=your_password \ -v /data/mysql:/var/lib/mysql \ mysql:8.0 \ --character-set-server=utf8mb4 \ --collation-server=utf8mb4_unicode_ci

数据目录一定要挂载到宿主机,否则容器删了数据就没了。我见过有人图省事不挂载,升级镜像时整个库存数据被清空的惨剧。

三是版本选择。我给工厂选型时建议直接用8.0系列,它是目前最新的LTS版本,性能和安全性都比5.7有明显提升。不要在生产环境追太新的小版本,稳定优先。

6.3 连不上数据库的几个高频原因:从socket到SSL

连接问题是工厂系统上线后最常见的求助内容。有次车间扫码终端报“Can't connect to local MySQL server through socket '/tmp/mysql.sock'”,很多人以为是数据库挂了,其实多半是客户端连服务器时走了socket文件路径,而MySQL服务端配置的socket路径不一致。排查时先在服务器上执行:

mysql -u root -p

如果服务器本机能连,再查my.cnf里socket路径是放在/tmp还是/var/run/mysqld/,客户端连接串里要指定对应的socket路径,或者直接用TCP方式连接:mysql -h 127.0.0.1 -P 3306 -u root -p。如果报ERROR 2002 (HY000),优先检查MySQL是否启动、socket路径是否一致。

远程连接另一个高频问题是权限和SSL。MySQL 8.0默认启用了caching_sha2_password认证和SSL相关配置,很多旧版客户端会报认证失败。如果工厂内网环境安全性可控,可在连接串里显式关闭SSL并指定兼容的加密方式。JDBC连接串加useSSL=false,或者用sslmode=DISABLED;认证插件如果一时改不了,可以在MySQL里为该账号显式指定mysql_native_password插件。

还有一类问题不是连不上,而是“连上了没多久就断”。这多半是应用层的连接池没有定期校验连接有效性。Java项目里用Druid或HikariCP时,要配置连接存活检测和空闲回收参数,防止MySQL侧wait_timeout把空闲连接切断后,应用层还在用死连接发请求。连接池最大连接数也别拍脑袋设,我建议根据车间同时操作人数估算,先用20~50的区间,压测后再调整,避免连接数设太高把MySQL内存吃满。

6.4 数据库版本升级与数据迁移的一个通用检查清单

后面工厂从单机版升级到多车间联网时,我们还做了一次MySQL服务器的迁移。总结一个检查清单,照着走基本不会出问题:

  • 迁移前执行mysqldump --single-transaction --routines --events,保证存储过程、触发器、事件一起导出;
  • 数据文件恢复后用CHECK TABLE和ANALYZE TABLE检查表完整性并刷新统计信息;
  • 对比迁移前后的表行数、关键查询耗时;
  • 先在一台测试机上模拟生产查询,再切正式流量;
  • 保留旧服务器至少7天,确认业务平稳后再下线。

这套流程对亮片厂,或者任何数据量在百万行以下的小型制造业都适用,不复杂,但每一步都不能省。

最后再多说一句。给工厂做管理系统,最难的地方往往不是MySQL本身,而是把数据库设计跟车间实际操作习惯对齐。你设计的批次追溯逻辑再严谨,工人扫码时不按规程操作,库存照样会飘。我们最终的解决方案是在每个领料枪上加了强制扫描批次号的红外校验——不允许手工输入,只能扫瓶身标签。数据在源头干净了,MySQL里做的一切约束、事务、触发器才有意义。这套“源头防错+数据库兜底”的组合,才是这类生产系统真正稳定的关键。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询