ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ERP开发必备:MySQL事务、锁与表结构实战精要

ERP开发必备:MySQL事务、锁与表结构实战精要 ERP开发这几年几乎是后端领域里最考验数据库功底的场景之一。很多做Java、PHP、Python的同学CRUD写得很溜一进ERP项目就露怯库存对不上、单据卡死、报表超时、并发超卖问题一个接一个。倒不是说ERP用了什么黑科技而是它对事务一致性、数据完整性、查询统计的要求比普通业务系统高一个量级。MySQL作为使用最广的开源关系型数据库在中小型ERP里占比极高。这篇文章就是给ERP开发人员准备的一份MySQL精简版实战基础把真正用得上的东西拎出来讲透砍掉那些日常压根碰不到的冷门概念。先说明一下我理解的“精简版”不是让你去背所有SQL语法而是围绕ERP业务里最常遇到的几个硬核场景来学。物料档案、BOM、采购单、销售单、出入库、库存账、资金账、报表统计、数据迁移这套东西跑通了你对MySQL的理解基本就够用了。文章会从环境配置、表结构设计、事务锁机制、报表查询、数据迁移与同步、常见故障排查这几个角度展开都是我在ERP项目里实际踩过坑、填过土之后沉淀下来的经验。1. 先想清楚ERP开发学MySQL和普通后端有什么不一样1.1 为什么ERP项目偏爱MySQL而不是Oracle、SQL Server很多老牌的ERP用Oracle或者SQL Server因为当年MySQL在存储过程、触发器、事务支持、锁机制上确实弱大并发高可用场景压不住。但MySQL从5.7到8.0InnoDB引擎越来越成熟加上开源的License成本和运维成本优势现在中小型ERP以MySQL为主力库的项目非常多尤其是互联网化、SaaS化的那批新ERP。选型上的核心考量有三个。第一是总拥有成本Oracle按CPU收费SQL Server按核数收MySQL是社区版免费采购一套中等规模ERP数据库License能省下几十万这对中小企业是实打实的决策因素。第二是生态Java技术栈的Spring全家桶对MySQL支持最好MyBatis Plus、Hibernate、Flyway这些工具全是围绕MySQL优化的。第三是运维友好度MySQL的安装部署、主从配置、备份恢复都简单直接一个中型团队花半天就能搭出一套主从环境同样的工作在Oracle上要研究好几周。当然MySQL也有短处。复杂分析查询、递归CTE的性能不如商业数据库但这恰恰说明做ERP开发时更需要主动管理SQL质量。我自己经手的几个项目凡是报表慢的九成不是MySQL不行而是业务逻辑写得不行走了全表扫描或者查询条件没吃索引。1.2 ERP业务把MySQL用在哪几个核心场景ERP系统通常包含基础资料、供应链、生产制造、财务成本、人力资源等模块。抛开行业差异底层对数据库的要求高度一致。第一是主数据管理。物料、供应商、客户、部门、仓库、员工这些基础档案需要稳定、可追溯、支持编码规则和唯一性校验。对应到MySQL就是大量字典表和唯一索引的应用。第二是单据流处理。采购订单、销售订单、生产工单、入库单、出库单每张单都是“表头表体”的主子表结构一单可能关联几十上百行明细。数据库要处理高频insert、update、delete还要保证单据状态流转时数据不出错。第三是库存与资金账务。每一次出入库都会产生库存流水每一笔收付款都会产生资金流水流水一旦生成就只允许追加、不允许修改删除月底还要做关账和成本核算。这块对事务和锁的依赖最深。第四是报表统计。库存汇总、销售分析、应收应付账龄、毛利成本核算全部依赖多表JOIN、聚合计算。在ERP里报表是老板看得最紧的东西慢一秒都有人催。把这四个场景吃透你再看任何ERP项目数据库层面的大盘基本就有了。2. 环境与基础配置装对版本建对字符集2.1 MySQL 8.0安装和初始化的注意事项ERP项目建议直接从MySQL 8.0起步而不是守着5.7。8.0在窗口函数、CTE、Hash Join、优化器上都有明显改进对复杂报表查询友好得多。另外8.0的默认字符集是utf8mb4从根上解决了5.7时代一堆编码兼容问题。安装时有两个点最容易翻车。一个是初始化密码策略8.0默认的validate_password组件会强制密码复杂度很多人在这一步折腾半天。本地开发图省事可以临时把强度调低但生产环境还是建议保留强密码策略。另一个是my.cnf的配置我一般会在一开始就把几个关键参数固定下来[mysqld] character-set-serverutf8mb4 collation-serverutf8mb4_0900_ai_ci default-time-zone08:00 max_connections500 innodb_buffer_pool_size1G innodb_log_file_size256M sql_modeSTRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZEROcharacter-set和collation定了字符集default-time-zone定时区innodb_buffer_pool_size是InnoDB引擎最重要的性能参数一般设为物理内存的50%到70%。sql_mode里的STRICT_TRANS_TABLES强烈建议保留它会阻止不合法的数据写入比如插入一个不存在的日期MySQL会直接报错而不是存一个空的9999值这在ERP里能挡住很多脏数据。装好之后一定要检查一下实际的全局变量的值因为有些系统是发行版自带的MySQL配置文件位置可能飘走。执行SHOW VARIABLES LIKE character%;和SHOW VARIABLES LIKE sql_mode;确认生效再动手建库。2.2 字符集编码90%的乱码问题出在建库这一步乱码是ERP上线后最常见、也最让人头大的问题。很多人遇到乱码第一反应是改表的字符集其实根子往往在建库那一步就埋下了。MySQL里的utf8实际上是utf8mb3只支持最多三个字节的字符真正的四字节字符emoji、部分生僻字、一些扩展区的汉字存不进去。比如客户名称里有一个生僻字“”用utf8mb3会直接报错或者显示成问号做导入的时候整批数据都得作废。8.0里默认的utf8已经指向utf8mb4但在老版本导出的SQL脚本里经常写着utf8迁移到8.0时还得手动改。实操中我的建库语句固定是这样CREATE DATABASE erp_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;注意八点零版本有一个拼写上的坑utf8mb4_0900_ai_ci这个排序规则是MySQL 8.0特有的如果你要和老系统做数据同步或者有DBA用5.7的工具连过来建议用utf8mb4_general_ci兼容性更好。建表的时候也不要单独去指定字符集跟着库走就行表级别的覆盖很容易出现“表是utf8mb4库是旧utf8”的怪问题。另外连接层的编码也会影响。Java的JDBC连接串上要拼characterEncodingutf8和useUnicodetrue否则程序写入的数据到库里就变乱码。这条我每次项目复盘都会提到因为它的排查路径特别隐蔽库、表、字段字符集都对就是连接参数少了characterEncoding。2.3 客户端工具和连接池参数怎么配ERP开发期和上线后常用的MySQL客户端无非Navicat、DBeaver、Workbench。Navicat功能全、上手快收费但小团队一般也能接受DBeaver开源免费跨平台适合预算有限的项目组。值得留个心眼的是如果你在用Navicat连接MySQL 8.0老版本的客户端可能会报缓存SHA2密码插件不支持的错升级到16.x以上或者换DBeaver就能解决。连接池是ERP后端开发里绕不开的环节。常用的连接池是HikariCPSpring Boot默认用它。给出的几个核心参数可以作为起步参考spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000maximum-pool-size不是越大越好连接池设太大反而增加数据库端的线程调度开销。一般按“并发请求峰值 × 单请求占用连接时间”来估算20到50足够绝大多数中小ERP使用。max-lifetime要小于MySQL的wait_timeout否则会有连接被服务端回收了、客户端还在用的尴尬局面。这里还有个容易忽略的点连接池给每个连接设置了空闲超时MySQL自身也有wait_timeout默认8小时两边时间不匹配会导致凌晨跑批任务时突然报Connection is not available。我见过好几个项目深夜定时任务失败最后查到就是因为连接提前被空闲回收了。3. 表结构设计一切围绕单据和流水转3.1 单据头与单据体主子表拆分的实战做法ERP里的业务单据基本都是一对多的结构。拿采购订单来说一张订单有供应商、订单日期、业务员、总金额这些公共信息同时包含多行物料、每行的数量、单价、税率、交货日期。如果做成一张表物料一多公共字段就会大量重复改一个供应商要更新几十行查询也有冗余。正确的做法是拆成两张表采购订单主表po_header存储公共信息采购订单明细表po_line存储明细行。主表一行对应明细表多行通过一个订单ID外键关联。CREATE TABLE po_header ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(30) NOT NULL, supplier_id BIGINT NOT NULL, order_date DATE NOT NULL, status TINYINT NOT NULL DEFAULT 0, total_amount DECIMAL(18,2) NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_order_no (order_no) ) ENGINEInnoDB;CREATE TABLE po_line ( id BIGINT PRIMARY KEY AUTO_INCREMENT, header_id BIGINT NOT NULL, line_no INT NOT NULL, item_id BIGINT NOT NULL, quantity DECIMAL(18,4) NOT NULL, unit_price DECIMAL(18,4) NOT NULL, tax_rate DECIMAL(5,2) NOT NULL DEFAULT 0, amount DECIMAL(18,2) NOT NULL DEFAULT 0, KEY idx_header_id (header_id) ) ENGINEInnoDB;主表加唯一索引uk_order_no防止单据号重复——这在ERP里是不可接受的错误。明细表在header_id上建普通索引就够了因为数据访问总是先按订单头ID过滤再拉全部明细行。金额字段用DECIMAL而不是FLOAT或者DOUBLE这是财务数据的基本常识浮点数算钱会产生精度误差0.1加0.2可能等于0.30000000000000004。查一张完整单据就是一次两表JOIN的问题SELECT h.id, h.order_no, h.supplier_id, h.order_date, h.status, l.line_no, l.item_id, l.quantity, l.unit_price, l.amount FROM po_header h LEFT JOIN po_line l ON l.header_id h.id WHERE h.order_no PO202501010001 ORDER BY h.id, l.line_no;主子表拆分看起来简单但很多新手会掉进另一个坑把明细表的主键设计成复合主键header_id line_no然后在所有业务代码里都拿着这个复合键去更新行。复合主键索引体积大、Join写法啰嗦还是用自增主键加唯一约束更省心。3.2 基础资料表物料、往来单位、编码规则的套路基础资料表在ERP里被称为“主数据”是整个系统数据的源头。物料表是最典型的代表里面除了物料编码、名称、规格型号之外还带单位、默认仓库、安全库存、采购价、销售价、启用状态这些控制字段。设计物料表时有一个重要原则业务主键和数据库主键分离。物料编码item_code在业务上要唯一、要能给人看、要符合编码规则但它不是数据库主键真正的数据库主键是无意义的自增ID或者雪花ID。这样做的原因是如果拿编码当外键去关联单据一旦编码规则调整比如中间加了一位分类码所有关联单据全部跟着遭殃。而用无意义ID做主键业务上怎么改编码都不影响历史单据。编码规则是ERP里很小的功能模块但几乎每个客户都会提需求。基本逻辑是“前缀 分类 日期 流水号”比如PO-2025-01-0001。在MySQL里的实现可以借助一张编号生成表用事务配合行锁来取号CREATE TABLE seq_no ( biz_type VARCHAR(20) PRIMARY KEY, current_no INT NOT NULL DEFAULT 0, reset_date DATE NOT NULL );START TRANSACTION; SELECT current_no FROM seq_no WHERE biz_type PO FOR UPDATE; -- 在程序里根据当前值计算新编号再UPDATE回去 UPDATE seq_no SET current_no current_no 1 WHERE biz_type PO; COMMIT;这里的SELECT ... FOR UPDATE非常关键它把编号生成的那一行锁住防止两个并发请求取到同一个流水号。编号生成这件事虽然小但并发一旦上来不用锁就会闹乌龙。3.3 库存流水表只追加、不修改、不删除库存账是ERP系统里最敏感的数据。无论采购入库、销售出库、生产领料、盘盈盘亏每一次变动都应该在库存流水表里留下一条不可篡改的记录。库存流水表的设计原则只有三个字只追加。CREATE TABLE inventory_transaction ( id BIGINT PRIMARY KEY AUTO_INCREMENT, trans_no VARCHAR(30) NOT NULL, item_id BIGINT NOT NULL, warehouse_id BIGINT NOT NULL, trans_type TINYINT NOT NULL, quantity DECIMAL(18,4) NOT NULL, before_qty DECIMAL(18,4) NOT NULL, after_qty DECIMAL(18,4) NOT NULL, customer_id BIGINT NULL, order_no VARCHAR(30) NULL, remark VARCHAR(255) NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_item_time (item_id, warehouse_id, created_at) ) ENGINEInnoDB;before_qty和after_qty记录的是变动前后的库存快照这个设计在排查库存差异时极其好用。假设某一条记录显示从100变成80你就知道这是一笔-20的减少如果发现库存对不上查流水一眼就能看到变化过程。注意不要在流水表上提供update和delete的业务接口这是个业务纪律问题不是SQL技术问题。有了流水表当前库存实际上可以用汇总视图来查询SELECT item_id, warehouse_id, SUM(after_qty - before_qty) AS stock_qty FROM inventory_transaction WHERE created_at 2025-01-31 23:59:59 GROUP BY item_id, warehouse_id;不过实际项目里不会只用流水汇总因为数据量大、查询慢常规做法是维护一张库存余额表每次出入库时用事务同时更新余额表和插入流水表。表的数量虽然多了一张但查询余额的速度是秒回的。4. 事务与锁库存和资金安全的第一道关卡4.1 事务的ACID如何在ERP业务里落地ERP里几乎每个核心操作都是一个事务。最简单的例子销售出库时既要扣减库存余额、又要写库存流水、还要更新销售订单状态这三个动作必须同时成功或者同时失败。一个成功一个失败账就平不了。MySQL的InnoDB引擎通过redo log保证持久性D通过undo log保证原子性和回滚能力A通过锁和MVCC保证隔离性I通过约束和存储引擎机制保证一致性C。用起来没那么神秘无非是在业务代码里显式开启事务把多个SQL包进去。Transactional public void issueGoods(GoodsIssueRequest request) { inventoryDao.lockStock(request.getItemId(), request.getWarehouseId()); int updated inventoryDao.deductStock(request.getItemId(), request.getWarehouseId(), request.getQuantity()); if (updated 0) { throw new BusinessException(库存不足); } inventoryTransactionDao.insertTransaction(buildTransaction(request)); salesOrderDao.updateStatus(request.getOrderId(), SHIPPED); }这里有个容易翻车的细节如果事务方法内部catch住了异常不往外抛Spring就会判定事务正常提交库存扣了但后面的SQL失败了却不会回滚。所以事务代码里异常一定要抛出来不要自己吞掉。4.2 隔离级别选默认就够吗MySQL InnoDB默认的事务隔离级别是REPEATABLE READ可重复读。很多从SQL Server或者Oracle转过来的人不习惯因为那两个数据库默认的是READ COMMITTED。其实UTF8mb4和排序规则的都提到了这是编码层面。现在MySQL的RR已经通过Next-Key Lock解决了大部分幻读问题所以直接用默认级别在ERP场景下是安全的还能保证同一个事务内多次查询结果完全一致。举个具体的例子。财务在月末核对当月销售订单总额先查了一次然后是另一个操作员修改了一张订单金额财务又查一次。如果隔离级别是READ COMMITTED两次查出来的金额会不一样财务就该困惑了。而RR级别下事务开始后第一次查询就生成了快照后面再查多次结果始终一致这对财务对账的体验非常重要。那是不是无脑用RR就行在高并发场景下RR可能因为间隙锁扩大锁范围导致并发性能下降。如果后面发现某些模块并发上不去、频繁死锁可以考虑把单独的会话或者单独的事务降成READ COMMITTED。但这是调优层面的操作不要一上来就全局改。4.3 行锁与乐观锁并发出库不超卖库存不足还发货是ERP里的大事故本质是并发问题。假设库存还有10件两个销售同时做出库如果程序没有加锁两边都读到10件各自扣减最后余额变成负数。最直接的解决办法是在事务里加行锁START TRANSACTION; SELECT id, stock_qty FROM inventory_balance WHERE item_id 1001 AND warehouse_id 3 FOR UPDATE; -- 程序检查 stock_qty 出库数量不够就回滚 UPDATE inventory_balance SET stock_qty stock_qty - 5 WHERE item_id 1001 AND warehouse_id 3; INSERT INTO inventory_transaction(...) VALUES (...); COMMIT;FOR UPDATE会把目标行锁住第二个事务执行相同的SELECT FOR UPDATE时会一直等待直到第一个事务提交或者回滚。这样并发出库就被串行化了不会超卖。行锁用起来要注意锁一定要走索引。如果WHERE条件里的字段没有索引InnoDB会退化成锁全表导致整个系统的写入全部互相阻塞。给item_id、warehouse_id建联合索引是必须的。还有一种不依赖数据库锁的方案是乐观锁在余额表上加version字段更新时带上版本号条件UPDATE inventory_balance SET stock_qty stock_qty - 5, version version 1 WHERE item_id 1001 AND warehouse_id 3 AND version 5;如果更新的影响行数是0说明版本号变了事务回滚让用户重试。乐观锁适合冲突概率低的场景比如扫码枪出入库。像出库这样核心的、冲突又密集的流程我还是更推荐行锁逻辑直观还能拿到锁等待提示方便排查。5. 查询与报表把统计数据算得又快又准5.1 多表关联类型与ERP报表场景ERP报表基本逃不开多表JOIN。销售排行榜要JOIN客户表、物料表、销售明细表库存查询要JOIN物料档案、仓库表、余额表。JOIN的类型要心里有数INNER JOIN两张表都匹配的行才返回适合对账和核销。LEFT JOIN左表全部行保留右表没有匹配就补NULL适合“以单为主看明细”比如订单列表带客户名称即使客户档案被误删也要能看到订单行。RIGHT JOIN用得少几乎可以用LEFT JOIN反转表顺序替代。CROSS JOIN笛卡尔积ERP里基本不会主动用但有时候写错条件会导致莫名其妙出现海量行排查时要能想到。多表关联最容易犯的错误是漏关联条件。三个表Join却只写两个关联条件结果会产生无意义的笛卡尔积行数。排查这类问题没有捷径只能看SQL的WHERE/ON条件一一对应。5.2 分组聚合的正确姿势报表的第二大类是统计汇总。按物料分类统计库存量、按月份统计销售金额、按往来单位汇总未开票金额这些都是GROUP BY加上聚合函数的活。SELECT MONTH(sales_date) AS month_no, customer_id, SUM(line_amount) AS total_amount FROM sales_order_detail d JOIN sales_order_header h ON h.id d.header_id WHERE sales_date 2025-01-01 AND sales_date 2025-04-01 GROUP BY MONTH(sales_date), customer_id ORDER BY month_no, total_amount DESC;写GROUP BY要注意SELECT中出现的非聚合列必须都出现在GROUP BY里这是SQL标准MySQL 8.0默认也是严格模式不会像5.7以前那样纵容你写出不规范SQL。如果要对分组结果做过滤用HAVING而不是WHERE。统计报表的性能靠索引支撑。这个查询最理想的索引是sales_date, customer_id联合索引MySQL能先按日期范围过滤再在索引内部分组。如果只有sales_date单列索引分组customer_id时就要回表。索引设计这件事没有银弹只能对着最慢的几条SQL用EXPLAIN调整。5.3 存储过程到底该不该用这个话题每次都要争论一轮。我的观点是在ERP开发里存储过程可以用但只用于固定流程和复杂对账逻辑不要拿来包办所有业务。存储过程的优势是紧贴数据库、跨应用共享、执行计划缓存高效。比如月末结账时要把库存流水归档、生成成本调整单、重算本月毛利这种几十行SQL串起来的固定流程写成一个存储过程nightly_close_month()让定时任务调用比在Java代码里拼几十次数据库交互要靠谱得多。但存储过程的坑也明显版本管理困难数据库里的代码没法用Git很好地做Review调试手段弱报错定位不如应用程序日志方便可移植性差将来若换数据库存储过程基本全部重写。所以业务复杂逻辑、单据状态流转、权限控制这些还是留在应用层数据库只做数据完整性约束和少量批处理。6. 数据迁移、备份与同步上线前后的硬仗6.1 Excel老数据导入清洗和映射比导入本身更重要ERP项目上线前十有八九要把老系统的数据迁过来。老系统是啥样的都有Excel手工台账、Access、老ERP导出的TXT五花八门。导入本身不难难的是数据质量和映射关系。实操流程我一般分四步。第一步把Excel数据导出为CSV用Excel的“另存为”功能或者Python脚本注意CSV编码要保存成UTF-8否则中文导入MySQL必乱码。第二步写SQL或者直接在工具里做校验查重复编码、非法日期、缺失的必填字段把脏数据列表拉出来给业务方清理。第三步做映射老系统的“商品编码”对应新系统的“物料编码”老系统的“单位”可能要枚举映射成新系统的“KG”、“PCS”等标准值。第四步正式导入。小数据量用Navicat的导入向导选好CSV文件、字符集、目标表就能导。大数据量用LOAD DATA INFILE更快LOAD DATA INFILE /tmp/items.csv INTO TABLE item_master FIELDS TERMINATED BY , OPTIONALLY ENCLOSED BY IGNORE 1 LINES (item_code, item_name, spec, unit, default_price);导入后立刻跑一遍校验SQL比如SELECT COUNT(*)、检查编码唯一性、核对总行数对比导出源的数据量确认一致才算完成。导入不是一次性的经常要反复调整规则重复导所以别怕流程繁琐每一步都留好校验逻辑。6.2 备份恢复与主从同步ERP的数据库备份是过了上线就要永远绷着的那根弦。我的建议是两条腿走路每天凌晨全量备份加上实时的binlog增量。全量备份用mysqldump注意加参数mysqldump -u erp_backup -p --single-transaction --routines --triggers --databases erp_db erp_db_$(date %F).sql--single-transaction让备份过程不锁业务表直接基于InnoDB的一致性快照生成备份文件能避开通宵备份时业务系统卡顿的问题。--routines和--triggers会把存储过程、触发器一起带出来缺了这两个参数恢复出来的库表面看数据都在跑批时却发现存储过程全失踪了。许多同学以为数据库同步非得买专用的同步软件其实MySQL主从复制就是现成的方案。生产库做为主库开一个只读的从库用于报表查询或者容灾配置也不复杂。关键是要把binlog格式设为ROW[mysqld] server-id1 log-binmysql-bin binlog_formatROWROW格式记录的是行级别的实际变更比STATEMENT格式更可靠不会因为某个存储过程执行顺序不一致导致主从数据漂移。从库的server-id改成不同的数字比如2。主从架起来之后从库可以用来做报表分析把高峰期的统计查询全部甩给从库主库的压力能缓解一大截。主从复制也不是万能的延迟是最常见的问题。从库追不上写入速度时报表数据会和主库有几分钟差距做一些实时性要求高的查询就要注意宁可直连主库也不要拿过期的从库数据误导业务。7. 高频问题排查实录乱码、锁等待、性能毛刺7.1 常见报错的定位思路ERP运维期碰到的MySQL问题翻来覆去就那么几类我整理成速查表放在这里可以参考着排查。现象常见原因快速处理方法写入中文变问号连接参数缺characterEncoding或建库字符集错检查SET NAMES utf8mb4、JDBC URL拼characterEncoding插入生僻字/emoji报错表字符集仍是utf8mb3整库转utf8mb4ALTER TABLE CONVERT TO CHARACTER SET utf8mb4报SELECT ... FOR UPDATE锁等待超时别的长事务占住行锁查询information_schema.innodb_trx找长时间未提交事务kill阻塞进程报SSL连接错误MySQL 8默认启用SSL旧客户端不支持JDBC URL加useSSLfalse或升级驱动连接池瞬间打满慢SQL拖住连接不释放抓慢日志杀掉慢查询优化索引主从数据不一致主从版本不同、或binlog_format不是ROW对齐版本设置binlog_formatROW必要时重建从库MySQL 8默认开启SSL很多老版本的JDBC驱动连上去直接抛SSL连接错误英文提示是类似Establishing SSL connection without servers identity verification is not recommended的警告有的驱动直接报错。解决办法不复杂要么升级驱动到8.x以上要么在连接串里加useSSLfalse再要么配置MySQL关闭SSL。生产环境如果都在内网而且有安全要求用加密连接更好但从研发角度先解决能连上的问题再谈安全。锁等待超时是并发ERP最容易遇到的报错提示像Lock wait timeout exceeded; try restarting transaction。可以先执行下面这句查谁占了锁SELECT * FROM information_schema.innodb_trx\G重点看trx_state、trx_started、trx_mysql_thread_id这三列。如果发现一个事务开了很久手动杀掉对应线程KILL 123456;从这里能看到应用里某个事务忘了提交。这类问题根治要回到代码层面检查事务边界确认异常情况是否回滚。7.2 慢查询分析与索引优化ERP跑久了会突然听到业务反馈“某张报表卡死了”。不要慌直接查慢查询日志SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 1;超过1秒的SQL会被记录下来。拿到慢SQL之后第一步是EXPLAINEXPLAIN SELECT h.order_no, l.item_id, l.quantity FROM po_header h LEFT JOIN po_line l ON l.header_id h.id WHERE h.order_date 2025-01-01 AND h.order_date 2025-02-01;EXPLAIN输出的关键列主要看type和rows。type出现ALL就是全表扫描出现index也要警惕尽量追到range或者ref。rows列估算扫描行数太大就要考虑加复合索引。以这个查询为例理想的索引是po_headerorder_date和po_lineheader_id前者过滤日期范围后者加速JOIN。如果还嫌慢再进一步思考查询的业务逻辑是否不可避免要扫描大量行比如直接SUM整张明细表这种场景可以在应用层或者从库里做汇总而不是硬压主库。实测优化效果时执行前先OPEN TABLE准备好IO缓存再跑EXPLAIN加实际执行时间对比优化前后。我见过不少人只看EXPLAIN就说搞定实际上因为缓存命中原因执行时间跟EXPLAIN并不总是严格对应最好多跑几次取平均值。关于这个项目我个人最深的体会是MySQL在ERP里的定位不是“一把梭”而是“规则执行者”。侵入性太强的业务规则放进数据库会让系统僵化但数据完整性、事务边界、并发控制这些基础能力必须依托数据库本身做扎实。我经手过一个销售出库并发超过预期的项目最初想用Redis锁来控制后来冷静下来改用数据库行锁加库存流水快照系统稳定运行一年多没有库存差异。所以基础功真的不能省把事务、锁、索引、字符集这几个章节学透ERP开发里的数据库坑你能避开一大半。
返回列表