ARTICLE DETAIL

资讯详情

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

ERP场景下MySQL基础:从表设计到索引优化

ERP场景下MySQL基础:从表设计到索引优化 1. 为什么要单独聊ERP场景下的MySQL基础先说一个我经常看到的现象很多刚入行的开发SQL写了不少但一进ERP项目就发懵。不是因为ERP里的SQL有多玄乎而是因为ERP系统的数据模型和常规互联网应用差别太大了——单表操作少多表关联是常态事务边界特别长数据一致性要求极高。你会在一个订单功能里同时牵扯到客户表、产品表、库存表、财务流水表一个操作下来可能横跨七八张表这时候如果MySQL基础不扎实写出来的代码要么慢得让人抓狂要么在并发场景下直接出数据错误。这篇内容不是要把MySQL官方文档翻译一遍而是站在ERP开发的实际场景把真正高频用到的知识点挑出来讲透。我覆盖的内容包括环境搭建、表结构设计、核心SQL操作、多表关联查询、事务控制、索引优化以及日常必不可少的备份恢复。看完之后你应该能独立维护一套中等规模的ERP系统数据库至少知道写SQL的时候哪些坑是绝对不能踩的。适合谁看刚转行做ERP开发的、在传统行业软件公司工作但数据库一直没系统学过的、以及从Oracle或SQL Server切到MySQL的开发者。如果你是资深DBA这篇可能太基础但用来做团队新人培训材料倒挺合适。2. 环境准备ERP开发用的MySQL要提前规划什么2.1 安装版本选择与编码配置ERP系统通常不是只有一个开发在用往往是团队协作、多环境部署。MySQL安装本身不难但有几个关键点要提前定下来不然后面改起来很痛。版本选择尽量用8.0以上的稳定版。8.0相比5.7有几点对ERP开发特别有意义窗口函数做报表统计很方便、公用表表达式复杂查询可读性大幅提升、默认字符集已经是utf8mb4。现在新项目我基本不会再看5.7除非是维护老系统。顺手提一句8.0的认证插件是caching_sha2_password老客户端可能连不上如果你用Navicat建议升级到16以上的版本如果是程序连接驱动也要选新版的。字符集必须用utf8mb4。说实话“utf8”在MySQL里是个历史遗留坑它实际上是utf8mb3只能存基本多语言平面BMP的字符遇到生僻字、emoji就会报错或者乱码。ERP系统里的客户名称、地址、备注什么奇怪字符都可能出现。建库的时候直接写上CREATE DATABASE erp_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;排序规则我用的是utf8mb4_unicode_ci。如果你对大小写不敏感比较有特殊要求可以换utf8mb4_general_ci但这个选择影响不大关键是字符集别选错。Linux服务器部署还有一个常用方式是用rpm包安装在离线环境或者内网部署时特别实用。基本流程是先去官网下载对应的rpm包然后按顺序安装核心是这几步rpm -ivh mysql-community-common-8.0.x.x86_64.rpm rpm -ivh mysql-community-libs-8.0.x.x86_64.rpm rpm -ivh mysql-community-client-8.0.x.x86_64.rpm rpm -ivh mysql-community-server-8.0.x.x86_64.rpm装完别忘了初始化mysqld --initialize systemctl start mysqld grep temporary password /var/log/mysqld.log注意MySQL 8.0在Linux下初始化和5.7有细节差别初始化后会生成一个临时密码第一次登录必须马上改掉不然没法继续操作。2.2 Windows与Linux环境下的部署差异Windows环境开发测试的话MySQL安装基本是下一步下一步的事情但有两个细节值得留意第一安装时选择Server only就够了不要装一堆用不到的组件减少后期维护成本。第二MySQL 8.0在Windows下默认数据目录和配置文件位置如果你需要自定义my.ini核心是设置好basedir和datadir同时把character-set-serverutf8mb4写进去。我曾经因为没提前配置最大连接数在项目演示的时候线上系统直接被压垮。max_connections这个参数默认151中小ERP几十个并发差不多够用但你的应用如果用了连接池连接数会远超你的预期。建议开发环境直接调整到300-500同时注意操作系统的文件描述符上限也要跟着调。这里给一个适合中小型ERP部署的my.ini关键配置参考[mysqld] port3306 basedirD:/mysql datadirD:/mysql/data character-set-serverutf8mb4 max_connections500 innodb_buffer_pool_size2G innodb_flush_log_at_trx_commit1 sql_modeSTRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION,ONLY_FULL_GROUP_BY关于innodb_flush_log_at_trx_commit这个参数多说两句取值1表示每次事务提交都刷盘最安全但性能开销最大取值2表示只写操作系统缓存每秒刷一次盘性能好但断电可能丢一秒数据。在做ERP这种交易类系统时我建议保持默认或者取1宁可慢一点不能丢数据。3. ERP开发最核心的表结构设计3.1 主数据表与业务数据表的分层ERP系统的表我习惯分为两大类主数据表和业务数据表。主数据表的特点是数据相对稳定、变动频率低、被大量业务表引用比如客户表、供应商表、物料产品表、仓库表、科目表。这类表的共同特点是必须要有唯一的业务编码字段比如物料编号同时要有启用/停用状态字段删除操作一律做成逻辑删除加废弃标记。业务数据表的特点是持续增长、具有时间属性、记录每一次业务行为比如销售订单表、采购入库表、盘点记录表、财务凭证表。这类表的核心就是记录“发生过什么”所以创建时间、操作人这些审计字段必不可少。举一个最常见的例子物料产品主数据表CREATE TABLE material ( id BIGINT PRIMARY KEY AUTO_INCREMENT, material_code VARCHAR(50) NOT NULL COMMENT 物料编码, material_name VARCHAR(200) NOT NULL COMMENT 物料名称, spec_model VARCHAR(200) COMMENT 规格型号, unit VARCHAR(20) COMMENT 基本单位, category_id BIGINT COMMENT 分类ID, status TINYINT NOT NULL DEFAULT 1 COMMENT 1启用 0停用, created_by VARCHAR(50), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_material_code (material_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT物料主数据表;注意这里用了UNIQUE约束保证物料编码唯一这是主数据表的基本素养。created_at和updated_at这种自动维护的字段能省掉应用层大量重复代码。另外status状态位是逻辑删除/停用的基础做ERP千万别物理删除主数据。3.2 主从表结构与外键怎么选ERP里最经典的数据结构就是单据头单据明细比如销售订单主表和销售订单明细从表。主表记录订单号、客户、订单日期、总金额这些汇总信息从表记录每一行产品的数量、单价、金额。设计这类结构时主从表通过一个业务单号字段做关联CREATE TABLE sales_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(30) NOT NULL COMMENT 订单号, customer_id BIGINT NOT NULL, order_date DATE NOT NULL, total_amount DECIMAL(12,2) NOT NULL DEFAULT 0, status TINYINT NOT NULL DEFAULT 0 COMMENT 0草稿 1已审核 2已完成 3已取消, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT销售订单主表; CREATE TABLE sales_order_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL, material_id BIGINT NOT NULL, quantity DECIMAL(12,4) NOT NULL, price DECIMAL(12,4) NOT NULL, amount DECIMAL(12,4) NOT NULL, KEY idx_order_id (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT销售订单明细表;关于外键约束FOREIGN KEY要不要用我一直的态度比较明确正式生产系统里物理外键能不用就不用。原因有三点第一ERP系统业务逻辑复杂很多关联关系是条件性的物理外键会限制数据流转的灵活性第二大数据量下外键约束的检查和维护有额外成本第三ERP系统经常要做数据导入、批量调整外键的存在会让这些操作变得很痛苦。但逻辑外键必须要建——加普通索引名字可以叫idx_order_id这种让查询性能有保障。经验物理外键在开发阶段如果用了上线之后每做一次批量数据变更你就会后悔一次。关系维护工作交给应用层数据库只保证存储和查询效率。3.3 字段类型选择经验谈ERP系统的数据类型选择跟别的系统不太一样有几个我踩过坑后固定下来的规则金额字段一律用DECIMAL比如DECIMAL(12,2)。千万别用FLOAT或DOUBLE浮点数的精度误差在金额计算上是致命的。12,2意味着最大可以存10亿级别的数值对绝大多数ERP业务足够。如果涉及更精细的单价可以扩大到DECIMAL(14,4)显示的时候再四舍五入。数量字段看业务需求有的需要精确到小数点后4位比如按重量计价的原材料有的只需要整数。直接用DECIMAL(12,4)最省心省得后面因为精度不足改表结构。日期时间字段区分DATE仅日期、DATETIME日期时间、TIMESTAMP三种。ERP里特别注意业务日期和数据创建时间要分开存储不要混用。比如订单日期是一个业务日期可能是指定某个过去的日期而created_at是真实创建时间。TIMESTAMP有2038年问题加上时区处理比较复杂我统一推荐用DATETIME。状态字段用TINYINT而不是VARCHAR1和0的语义在代码层面对应枚举数据库层面保持简洁。不要用VARCHAR存ACTIVE/INACTIVE这种字符串浪费空间且查询效率低。接下来看核心SQL操作在ERP语境下这些操作跟互联网应用有显著差异。比如ERP里的INSERT往往是主从表一起操作用户新增一张销售订单的同时要插入N条明细记录这些在开发上要在一个事务里完成。4. 增删改查在ERP里的实际形态4.1 INSERT的进阶用法基础的单行INSERT就不多讲了ERP开发中更常用的是批量插入和插入后获取自增ID。主从表同时插入的经典场景新建销售订单插入主表拿到订单ID然后循环插入明细表。这里有个关键点怎么拿到刚刚插入的主表IDINSERT INTO sales_order (order_no, customer_id, order_date, total_amount) VALUES (SO20240116001, 1001, 2024-01-16, 0); SET order_id LAST_INSERT_ID();LAST_INSERT_ID()是连接级别的函数你不需要担心并发情况下会拿到别人的ID——每个连接算自己的这是MySQL设计好的行为。但是注意不要在一条INSERT语句里混合插入多张表再取ID那样取到的是最后一张表的自增ID容易出错。批量插入明细时ERP通常会有这种需求导入Excel里的多行数据到一张表。语法很简单INSERT INTO sales_order_item (order_id, material_id, quantity, price, amount) VALUES (1001, 2001, 10, 15.50, 155.00), (1001, 2002, 5, 20.00, 100.00), (1001, 2003, 2, 33.25, 66.50);相比逐条插入批量插入的性能提升是数量级的。如果明细动辄上千行这个习惯能帮你避免很多性能问题。还有一个很实用的写法是INSERT ... ON DUPLICATE KEY UPDATE它解决的是“有则更新无则插入”的场景。比如ERP系统每晚会从外部系统同步库存快照这时就能用上INSERT INTO stock_snapshot (material_id, stock_qty, snapshot_date) VALUES (2001, 150.5, 2024-01-16) ON DUPLICATE KEY UPDATE stock_qty VALUES(stock_qty);素材这里的stock_snapshot需要给material_id和snapshot_date建联合唯一索引这个语句才能生效。4.2 UPDATE和DELETE的安全红线ERP开发里所有UPDATE和DELETE操作最重要的一个习惯就是先SELECT确认范围再执行更新或删除。这句话我听很多老前辈说过但直到自己把生产数据改错了才开始真正执行。比如做订单审核功能要把订单状态从0草稿改成1已审核同时加工库存。这个操作核心就是UPDATEUPDATE sales_order SET status 1, updated_at NOW() WHERE order_no SO20240116001;如果WHERE条件漏写那就是全表状态更新这种情况在ERP里就是事故。我建议在开发期把sql_safe_updates这个会话参数打开SET sql_safe_updates 1;这个参数开启后不带WHERE条件的UPDATE和DELETE会被拦截除非有LIMIT等于给手滑加了保险。生产环境如果担心影响某些特殊操作可以在会话级别临时设置。删除操作在ERP里的态度很明确业务数据永远软删除。所谓软删除就是加一个deleted标记字段ALTER TABLE sales_order ADD COLUMN deleted TINYINT DEFAULT 0 COMMENT 0正常 1已删除;查询的时候统一带条件WHERE deleted 0。这样即使误删也能恢复而且保留审计痕迹。只有那些确实无用的临时表、测试表才考虑物理DROP / DELETE。4.3 SELECT查询与排序分页ERP查询有几个高频场景订单列表查询、库存查询、报表统计。这些查询的共同特点是条件多、维度杂查询条件通常来自页面搜索框。排序直接看这个最常见的需求按创建时间或者业务日期排序。注意如果在日期字段上有索引直接用ORDER BY date_field DESC性能尚可但如果排序字段没索引且数据量大就会产生文件排序Using filesort这个问题在后面“索引优化”部分细说。分页查询是另一个问题。传统的LIMIT分页在大数据量下会越来越慢因为MySQL要扫描并丢弃前面的所有行。比如LIMIT 100000, 20它实际上要读前面100020行然后把前10万行扔掉。ERP里的处理办法有两个一种是用自增主键做游标分页-- 第一页 SELECT * FROM sales_order WHERE deleted 0 ORDER BY id LIMIT 20; -- 第二页记住上一页最后一个id SELECT * FROM sales_order WHERE deleted 0 AND id 1020 ORDER BY id LIMIT 20;另一种是用覆盖索引延迟关联先只查ID再关联完整数据SELECT so.* FROM sales_order so INNER JOIN (SELECT id FROM sales_order ORDER BY id DESC LIMIT 100000, 20) t ON so.id t.id;这两种方式在ERP列表页大数据量百万级时性能差距可以达到几十倍。5. 多表关联查询ERP的灵魂5.1 JOIN的基础与常见坑ERP几乎没有单表查询就能搞定的业务。查销售订单要关联客户表看客户名称查库存要关联仓库表和物料表查采购入库要关联供应商表。所以JOIN是ERP开发必须掌握的技能。从基础开始梳理。INNER JOIN取交集LEFT JOIN以左表为基准RIGHT JOIN以右表为基准实际项目中RIGHT JOIN很少用能用LEFT JOIN反转就反转。这些概念面试时背得很流利但写SQL时的实际问题远不止这些。第一个坑关联字段的字符集和排序规则不一致。如果两张表的关联字段一个是utf8mb4一个是utf8JOIN会报错或者隐式转换导致索引失效。这说明建表时统一字符集很重要尤其是老项目迁移时经常遇到这个坑。第二个坑多表JOIN的过滤条件放错位置。LEFT JOIN时如果筛选右表字段的条件写在WHERE里左连接就退化成内连接了因为WHERE条件把NULL行过滤掉了。很多人刚开始没意识到这个问题查出来的结果数量不对。需要保留左表全部记录时条件应该放到ON部分SELECT c.customer_name, o.order_no FROM customer c LEFT JOIN sales_order o ON o.customer_id c.id AND o.status 1;第三个坑JOIN顺序和统计偏差。ERP的报表经常需要统计汇总某段时间内每个客户的订单总额。这时候GROUP BY的统计口径要和业务口径对齐。5.2 子查询与聚合统计实战ERP的报表里有一种查询非常典型查“有订单但订单金额超过某阈值的客户”或者“某产品最近仨月的销售趋势”。先看子查询。用一个典型的场景查询每个客户最新的订单信息。SELECT so.*, c.customer_name FROM sales_order so INNER JOIN customer c ON so.customer_id c.id WHERE so.id IN ( SELECT MAX(id) FROM sales_order WHERE deleted 0 GROUP BY customer_id );这里用IN子查询找每个客户的最大订单ID再回原表取详情。相比直接在外层GROUP BY这个写法在MySQL的某些执行计划下反而更高效。聚合统计则是另一个高频场景。比如按月份统计每种物料的销售数量SELECT DATE_FORMAT(order_date, %Y-%m) AS month, material_id, SUM(quantity) AS total_qty, ROUND(SUM(amount), 2) AS total_amount FROM sales_order_item oi INNER JOIN sales_order so ON oi.order_id so.id WHERE so.status IN (1, 2) AND so.deleted 0 AND so.order_date BETWEEN 2024-01-01 AND 2024-12-31 GROUP BY DATE_FORMAT(order_date, %Y-%m), material_id ORDER BY month, total_amount DESC;几个细节提醒group by用的表达式和select里的表达式要完全一致否则MySQL会因为ONLY_FULL_GROUP_BY报错或者结果不符合预期。日期字段如果有索引用DATE_FORMAT包裹后索引就失效了但ERP报表量级通常能接受更讲究时可以用日期范围桶表或冗余年月字段。MySQL 8.0的窗口函数也值得推荐比如计算累计销售、排名、同环比。用ROW_NUMBER()给每个客户按订单时间排序就能很方便地查出每个客户最近5笔订单SELECT customer_id, order_no, order_date, ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC) AS rn FROM sales_order WHERE deleted 0;窗口函数在处理这种“分组内排序取TopN”的需求时比传统自连接和子查询的写法简洁太多推荐ERP开发认真学习。6. 事务控制ERP数据一致性的命门6.1 ACID与事务边界ERP系统最不能让用户接受的就是数据对不上账。库存明明扣了订单却显示没审核客户付款了但财务那边看不到流水。这些问题根源基本都在事务边界没控制好。MySQL事务的核心是ACID四个特性原子性Atomicity、一致性Consistency、隔离性Isolation、持久性Durability。正好可以用一个典型ERP场景贯穿理解销售订单审核扣减库存生成应收流水。这三件事是绑定在一起的要么全部成功要么全部回滚这就是原子性。如果扣库存成功但订单审核失败回滚了库存却已经变了那数据就乱了。MySQL里InnoDB引擎默认支持事务MyISAM不支持这是MyISAM不适合做ERP的关键原因之一。MySQL默认的autocommit是开启的你执行一条UPDATE也会立即提交。所以当你需要多步操作绑定时必须显式开启事务START TRANSACTION; UPDATE sales_order SET status 1 WHERE order_no SO20240116001; UPDATE inventory SET stock_qty stock_qty - 10 WHERE material_id 2001 AND warehouse_id 5; INSERT INTO ar_ledger (order_no, amount, status) VALUES (SO20240116001, 155.00, 0); COMMIT; -- 如果任何一步出错 ROLLBACK;实际开发中事务操作是由应用层控制的但无论用什么语言记住这个原则事务要短不要在一个事务里做大量耗时的外部调用比如请求外部接口、等待用户输入。6.2 隔离级别与并发控制事务隔离级别直接影响并发场景下的数据表现。MySQL InnoDB支持四种隔离级别读未提交READ UNCOMMITTED、读已提交READ COMMITTED、可重复读REPEATABLE READMySQL默认、串行化SERIALIZABLE。ERP开发最关心的两个问题脏读和幻读。脏读是读到了别人未提交的数据。比如A事务改了库存还没提交B事务查询库存看到了修改后的数如果A事务又回滚了B就拿着假数据做了决策。默认隔离级别下不会出现脏读这点放心。幻读是同一事务内两次同样条件的查询结果集不一样。比如你把订单状态从0全改成1的过程中另一个事务又插入了一条status0的订单你再查的时候发现多了一条。MySQL的InnoDB在可重复读隔离级别下通过间隙锁Gap Lock一定程度上解决了幻读问题。实际开发中的建议使用MySQL默认的REPEATABLE READ就好绝大多数ERP业务在这个级别下都能正确处理。不要轻易改全局隔离级别如果特定场景需要更强的隔离就在事务开始时单独设SET TRANSACTION ISOLATION LEVEL READ COMMITTED;并发控制还要注意行锁竞争问题。比如多个用户同时抢购同一个物料修改同一个库存行数据库会行锁等待。表现为某个用户的操作一直卡住直到另一个事务提交。遇到这种场景优化方向是减少事务持锁时间、合理拆分操作。也可以考虑用库存流水表来记录“锁定数量”而不是直接改库存余量等真正扣减时再处理不过这是更高级的设计了。注意MySQL默认的隔离级别下普通SELECT是快照读MVCC不锁定行。如果需要锁定某一行等待事务结束再读要用SELECT ... FOR UPDATE。ERP的“锁单审核”功能如果要做防并发操作这就是关键工具。6.3 优化建议与锁等待处理实际运维时会碰到一个最典型的事务问题锁等待超时。错误信息大致是Lock wait timeout exceeded; try restarting transaction。这个错误在ERP上线初期特别常见我的排查思路一般是先用SHOW ENGINE INNODB STATUS看当前有没有锁等待用information_schema.INNODB_TRX查询当前活动事务和运行时间找到异常事务对应的连接确认是哪个业务场景产生了长时间未提交的事务和业务方确认后KILL掉异常连接解除锁等待。一个常见元凶是代码里开启了事务但忘记COMMIT或ROLLBACK常见于异常处理只写了日志没回滚事务。排查时特别留意这个。7. 性能优化与索引的正确姿势7.1 索引设计的第一性原理ERP的数据量增长很快而且查询模式相对固定索引设计做得好不好直接影响上线后的用户体验。索引的核心是BTree通过减少数据扫描范围来提速。你可以把索引理解成书的目录——没有目录就得逐页翻书有了目录直接翻到对应章节。创建索引的方式很简单ALTER TABLE sales_order ADD INDEX idx_customer_id (customer_id); ALTER TABLE sales_order_item ADD INDEX idx_order_id (order_id); ALTER TABLE sales_order_item ADD INDEX idx_material_id (material_id);但哪些字段要建索引这是有讲究的。几个实用原则第一高频查询条件字段要建索引。比如sales_order表的customer_id、order_date、status这些字段经常出现在WHERE里都值得建索引。第二满足最左前缀原则。联合索引a, b, c会为(a)、(a, b)、(a, b, c)这三种条件组合提供加速但单独用b或c条件时索引无效。所以在建联合索引时把最常用的过滤字段放最前。第三尽量避免在索引列上做函数运算。比如WHERE DATE(order_date) 2024-01-16会让索引失效应该改成WHERE order_date 2024-01-16 AND order_date 2024-01-17。建索引很容易删索引也容易难点是判断该建哪些。我的做法是从慢查询日志入手开启慢查询找到执行时间长的SQL一个个分析它们的WHERE条件和JOIN条件针对性地补索引。-- 开启慢查询记录 SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 1;7.2 EXPLAIN执行计划怎么看EXPLAIN是MySQL优化最核心的工具它告诉你SQL会怎么执行是走索引还是全表扫。简单说重点看这几个字段type表示访问类型从好到差依次是system const eq_ref ref range index ALL。看到ALL就要警惕那是全表扫描。key实际使用的索引名如果是NULL表示没用到索引。rows预估扫描行数越小越好。Extra如果出现Using filesort或者Using temporary查询可能要优化了。举一个实际排查的例子。业务反馈说客户订单列表越翻越慢我拿到慢SQL后用EXPLAIN一看EXPLAIN SELECT * FROM sales_order WHERE customer_id 5001 ORDER BY order_date DESC LIMIT 20;结果显示type是ref用到了idx_customer_id但Extra里出现Using filesort——说明order_date字段没有索引参与排序MySQL要额外做一次文件排序。优化方案是建联合索引让排序也走索引ALTER TABLE sales_order ADD INDEX idx_customer_date (customer_id, order_date);再EXPLAIN一次Using filesort消失了。这就是性能优化的典型手法。7.3 常见SQL性能反模式我见到太多ERP开发写SQL时踩同样的坑挑三个最常见的**SELECT ***。这条习惯的危害不仅在于多查了不需要的字段更关键的是它破坏了覆盖索引的优化机会。假如SELECT只查几个字段而这个字段组合正好在索引里MySQL可以不走表回查速度会快很多。建议写出明确字段列表这个习惯也方便后续排查。大表JOIN不加条件。两个几百万行的表直接JOIN如果不加WHERE过滤MySQL可能得生成一个极大的中间结果集。这种查询把系统拖垮的故事我见过不少次一定要先在子查询或ON条件中缩小数据范围。分页越翻越慢。LIMIT 100000, 20为什么会慢前面已经讲过。再强调一次数据量起来后这个分页方式必须换成游标式或延迟关联。这些优化点的效果用一个实际数据说明某模块原来订单列表查询平均要2.8秒通过补索引改分页方式平均降到0.05秒以下。这不是极端案例是常规优化就能达到的效果。8. 备份恢复与日常维护8.1 用mysqldump做逻辑备份备份是ERP开发不能不看的内容因为大多数企业ERP系统的数据库宕机伤害是难以估量的。虽然日常备份通常由DBA或运维负责但开发人员至少要有手工备份的意识。MySQL自带mysqldump工具能做逻辑备份用法很简单mysqldump -u root -p --single-transaction erp_db /backup/erp_db_20240116.sql--single-transaction参数对InnoDB表特别重要它通过开启一个一致性的读事务来备份备份过程中不会锁表。如果你的备份计划是每天凌晨执行加上这个参数基本不影响业务。恢复就更简单了mysql -u root -p erp_db /backup/erp_db_20240116.sql注意恢复前先确认目标库的字符集和排序规则一致跨服务器迁移时特别容易在这个地方出问题。8.2 数据导入导出与结构变更ERP开发经常要处理Excel导入数据物料清单、期初库存、客户列表。客户端工具如Navicat提供导入向导方便但性能一般。数据量大时我更喜欢先把Excel转换成CSV然后用LOAD DATA INFILE导入LOAD DATA INFILE /tmp/material_batch.csv INTO TABLE material FIELDS TERMINATED BY , ENCLOSED BY LINES TERMINATED BY \n (material_code, material_name, spec_model, unit, category_id, status);这种方式的导入速度比客户端工具快很多几十万行的物料数据基本秒级完成。结构变更则要特别注意ERP系统运行期间改表结构会锁表。8.0之前ALTER TABLE是拷贝重建的方式表的行数多时会锁很长一段时间。8.0优化了不少但还是建议在业务低峰期执行结构变更而且先把变更放到测试环境验证一遍再上生产。8.3 日常性能巡检清单给ERP开发一份简单实用的日常巡检清单检查项简单判据建议动作慢查询日志long_query_time超过1秒的SQL数量分析并优化对应SQL或索引连接数max_connections是否长期占满冲击高时监控优化连接池磁盘空间数据目录剩余空间定期清理归档日志和binlog表碎片OPTIMIZE TABLE是否有必要大型表定期整理备份可恢复性是否真做过恢复演练定期实测恢复流程宁可做一个月的备份也比不做备份强。更要紧的是每季度做一次恢复演练——备份文件不能恢复等于没有备份。9. ERP开发必须绕开的6个MySQL实践陷阱WHERE IN子查询在数据量大时性能不稳定尤其是子查询结果集上万行时。尽量改成JOIN写法或者用临时表代替。字段名和关键字冲突。order是MySQL关键字如果你的表里字段名用了这类词写SQL时必须反引号包裹。建表时一定要避开不然后续所有代码都带着引号维护起来让人抓狂。日期范围查询的边界问题。查“1月整月数据”时正确写法是 2024-01-01 AND 2024-02-01不要写成 2024-01-31这样会漏掉1月31日23:59:59之后的数据。group by order by组合时MySQL对SQL模式的敏感。开启ONLY_FULL_GROUP_BY后SELECT的列必须是group by的列或聚合函数。写SQL前先确认线上SQL模式不然换个环境同一套SQL就报错。float类型存储货币的精度错误。前面强调过金额用DECIMAL这里再次强调任何需要精确计算的数值包括税率、折扣率、单价都不要用浮点类型。忘记加WHERE条件做批量更新。sql_safe_updates在生产环境可能被禁掉所以靠人不如靠习惯——每次业务更新前先写SELECT验证影响行数。用一句我在团队培训常说的话收尾MySQL基础知识的价值不在于你能背出多少语法而在于你在写每个SQL、设计每张表时能够想到它在上线半年后数据量翻倍时还能不能扛得住。ERP系统生命周期很长代码可以重构表结构却往往要为多年前的设计买单。所以从第一天开始就用对待长期资产的心态去设计你的数据库。
返回列表