我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

数据库实验五完整实践:解压、建库、事务与避坑指南

数据库实验五完整实践:解压、建库、事务与避坑指南 简介面向西北工业大学软件学院数据库课程学习者这份压缩包完整收录实验五的提交材料与过程记录围绕电子商务数据库设计任务提供ER图、概念数据模型及配套文字说明可帮助完成从需求分析到概念结构设计的闭环。压缩包共20个文件约282KB以gif操作录屏为主14个附doc设计文档、txt说明文本以及cdm概念模型和htm页面便于对照界面流程理解每一步实验结果。目前已有909人学习下载适合正在做该实验或复习数据库概念设计的同学参考。内容包含注册、登录、购物车、订单确认、支付信息、历史订单等关键页面录屏配合ER图与文本解析清晰呈现实体、联系及约束的建模思路doc文档则对E-Commerce项目描述和实验结果做了归纳cdm文件可用建模工具打开继续修改方便在此基础上扩展自己的设计方案。1. 西北工业大学软件学院数据库实验五.zip先别急着解压先分清这是任务包还是工程包拿到西北工业大学软件学院数据库实验五.zip这类资源包很多人的第一反应是右键解压、看看里面有什么然后双击某个脚本或者打开某个文档。我建议你先按住这个冲动。这个 zip 不是普通课件压缩包它更像一次“带基础设施的课程设计交付物”里面通常有实验指导说明、建表 SQL、代码骨架有时还夹着数据文件和配置文件。你以为解压完就开始了其实真正的坑往往在解压之后、连库之前。这篇文章会以这个实验五 zip 为主线讲清楚解压、建库、增删改查、事务、避坑和验证的完整路径适合正在写数据库实验五、拿到包但不知道从哪下手的人或者提交前想排查一遍的同学。2. 把实验五 zip 安全落地完整性校验、目录识别与字符集乱码处理2.1 zip 完整性校验用unzip -t和 Python 双重确认实验五的 zip 如果是在课程平台或者 QQ 群里传的很容易出现两种情况下载到一半中断或者压缩包本身被压缩软件“修复”过但 CRC 校验不通过。直接解压可能出现缺文件、SQL 脚本截断、代码文件读到一半报语法错误之类的怪问题。这些问题到后边才暴露排查成本非常高。我一般会先做一次完整性校验。cd ~/Downloads unzip -t 西北工业大学软件学院数据库实验五.zip如果包完整unzip 会在最后打印No errors detected in compressed data。如果看到bad CRC、mismatching filename这类输出说明压缩包里的数据已经损坏不要继续解压重新去课程平台下载才是最快的办法。Windows 下没有 unzip 的话可以用 Python 自带的标准库python -m zipfile -t 西北工业大学软件学院数据库实验五.zippython -m zipfile -t会把 zip 当作 zipfile 模块的测试对象逐个 entry 检查 central directory 和本地文件头是否匹配。它的优点是跨平台不需要额外安装工具缺点是有些国产压缩软件生成的 zip 能在 Windows 资源管理器里解压但 zipfile 模块会报BadZipFile: Bad magic number因为文件头里有非标准字段。这时候我建议用 7-Zip 打开它看一眼而不是急着删。7-Zip 对不规范 zip 的容忍度比标准库高很多文件头多几个字节、crc 位置偏移它通常能解出来。但你要知道这种“能解出来”不代表内容没有丢解完以后一定要看文件大小和目录数量是否和资源包发布时一致。如果不知道原始文件数至少看看有没有 readme 或者实验说明文档并且能正常打开。2.2 识别资源包结构说明文档、SQL 脚本和代码骨架的三角关系这类课程资源包的结构通常逃不开三样东西实验说明文档、SQL 脚本、代码骨架。常见的形态会是这样我给你画一个“常见形态举例”不是你这个包一定长这样但可以作为对照参考西北工业大学软件学院数据库实验五.zip ├── 实验指导书.pdf ├── schema.sql ├── data.sql ├── src/ │ ├── main.py │ └── db.py └── report_template.docx先找实验指导书它决定了你这次实验的验收目标。数据库实验五在不同年份可能被设计成不同的侧重有的是“数据库设计与实现”要求你交 ER 图 关系模式 建库脚本有的是“数据库应用编程”要求在 Java 或 Python 里完成增删改查和事务还有的是“存储过程与触发器”重点在 PL/SQL 或 T-SQL。同样是实验五去年和今年的要求可能完全不一样。这里的判断逻辑很简单先看指导书里有没有“题目背景”和“验收指标”两个小节。有题目背景说明重点在业务规则和表结构设计只有验收指标和提交格式说明重点在程序逻辑和运行结果。这一步会决定你后边花时间在 SQL 脚本还是应用代码上。SQL 脚本要分清schema.sql和data.sql。带schema的是建表、建约束、建索引的 DDL带data的往往是 INSERT用来造测试数据。很多同学上来就把整个 sql 文件导进数据库结果要么外键报错要么主键冲突。正确做法是分两次跑先跑 schema再跑 data。如果指导书里说“不允许修改初始数据”那你连 data.sql 都不该改。代码骨架里的db.py或者DBUtil.java是数据库连接入口别急着改参数以外的东西。先确认它用的是 JDBC、ODBC 还是 pymysql这决定了你本机要不要装对应驱动。2.3 文件名与内容乱码GBK、UTF-8 和 zip 伪加密的连环坑实验五 zip 解压后文件名乱码是特别常见的事因为制作压缩包的人用的可能是 Windows 自带“发送到压缩文件”压缩时文件名用的是本地 ANSI 编码也就是 GBK。而 Linux 和 macOS 的默认解压工具默认按 UTF-8 解码于是文件名变成Î÷±±¹¤Òµ´óѧ这类乱码。如果在 Linux 下遇到这种情况可以用 unzip 的-O参数直接指定文件名编码unzip -O GBK 西北工业大学软件学院数据库实验五.zip -d lab5-O GBK的意思是让 unzip 用 GBK 编码解释 zip 里的文件名。注意-O只影响文件名不影响文件内容的编码。文件内容乱不乱是 SQL 脚本和文档本身存的时候用了什么编码的问题跟压缩过程没关系。如果连-O都找不到可以用 Python 自己处理文件名解码import zipfile with zipfile.ZipFile(西北工业大学软件学院数据库实验五.zip) as zf: for info in zf.infolist(): raw info.filename.encode(cp437) # zipfile 默认按 cp437 解码 try: decoded raw.decode(gbk) except UnicodeDecodeError: decoded raw.decode(utf-8, errorsreplace) print(decoded) zf.extract(info, lab5_redecode)这里有一段很多人都不知道的细节Python 的 zipfile 读文件名时并不会自动还原 GBK它先把原始字节按 Latin-1/cp437 转成了 str所以你要先encode(cp437)拿回原始字节再尝试 GBK 解码。不是直接把info.filename拿去decode(gbk)就能成功这是新手最容易踩的坑。还有一种是 zip 伪加密。有些压缩软件为了处理半损坏文件会把 zip 的 general purpose bit flag 第 0 位置 1看起来像有密码实际没有。玩过一点逆向的人可能听过“伪加密”这个词实验资源包里也偶尔出现。你可以用 Python 检查每个文件的加密标志位import zipfile with zipfile.ZipFile(西北工业大学软件学院数据库实验五.zip) as zf: for info in zf.infolist(): encrypted bool(info.flag_bits 0x1) print(info.filename, 伪加密/加密标记 if encrypted else 正常)如果 flag_bits 显示0x1但解压时不输入密码也能出文件说明是伪加密。用 7-Zip 直接右键解压通常能绕过或者在 Python 里解压时把pwdNone试一下。真正带密码的 zip 没有密码就是解不开不用硬试。3. 从 ER 图到可执行 SQL让实验五的建库脚本直接跑通3.1 范式建模不是交差实体、联系和主外键设计评审数据库实验五不管题目是什么最核心的交付物永远是数据库结构设计。很多同学抄一个现成的建表脚本就往里灌数据结构对不上题目到了验收提问环节才翻车。我的习惯是先画一张“纸面 ER 图”哪怕老师不要求交也要画给自己看。实体就是业务里的名词学生、教师、课程、订单、商品、仓库这些都是实体。联系是动词选课、借阅、购买、入库。判断联系类型时有一个口诀一对一就哪边都可以放外键一对多就在多端放外键多对多必须建中间表。实验五最容易在“联系是否隐含”上扣分。比如选课这个联系表面上是学生和课程的多对多所以需要一张sc(student_id, course_id, score)中间表。如果业务里还要求记录“哪个老师给这个学生上的这门课”那授课就不是单纯的多对多而是把老师也卷进来成了三元联系。这时候中间表还得加teacher_id。范式不是越多越好。我在课程设计评审里见过有人把一张九字段表拆成六七张表说“满足 BC 范式”结果一条查询 join 五次代码里全是关联。实验五的时间有限一般情况下满足第三范式就够。遇到“订单总金额”这种能从单价和数量算出来的字段不要存遇到“学生姓名”这种已经存在学生表里的字段不要在订单表里再来一份。这是设计层面的复盘点每张表除了主键以外其他字段是不是都完整依赖主键而不是依赖别的非主键字段。3.2 选 MySQL 还是 SQL Server看资源包里的脚本方言再做决定西北工业大学软件学院数据库课常用的教学环境有 MySQL 和 SQL Server 两套。不是哪个“更好”就选哪个而是要看资源包里的 schema 脚本用的是哪门方言。如果 schema 文件里出现IDENTITY(1,1)、NVARCHAR、GETDATE()那这是 SQL Server 的 T-SQL如果出现AUTO_INCREMENT、ENGINEInnoDB、ON DUPLICATE KEY UPDATE那这是 MySQL。以本机装哪个为准再看指导书里写的验收环境。一个最简单的判断表功能点MySQL 8.xSQL Server 2019自增主键AUTO_INCREMENTIDENTITY(1,1)字符串VARCHAR/TEXTVARCHAR/NVARCHAR系统时间NOW()GETDATE()分页LIMIT offset, countOFFSET ... ROWS FETCH NEXT修改表结构ALTER TABLE ... MODIFYALTER TABLE ... ALTER COLUMN这个表不是为了让你背语法是让你在用“百度搜到的解法”的时候能快速分辨能不能用在你的环境里。搜AUTO_INCREMENT出来的 SQL 在 SQL Server 里运行会直接报语法错误不是你的数据库坏了。如果你两种都没装我建议选 MySQL 8 Navicat 或者 DataGrip 的组合。原因很简单MySQL 8 对 utf8mb4 的支持好JDBC 和 pymysql 都有成熟驱动而且实验五如果要求写应用层代码MySQL 在整个链路里几乎没有幺蛾子。装完以后第一件事不是建库是确认端口能连上mysql -h 127.0.0.1 -P 3306 -u root -p能进mysql提示符说明服务正常。3.3 建库建表脚本约束、索引和导入顺序的代码级检查拿到 schema.sql 之后不要直接整个文件跑完就完了。你要把建库和建表脚本拆开看。下面是我建议的脚本执行顺序以 MySQL 为例CREATE DATABASE IF NOT EXISTS lab5 DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; USE lab5; SET FOREIGN_KEY_CHECKS 0; DROP TABLE IF EXISTS sc; DROP TABLE IF EXISTS student; DROP TABLE IF EXISTS course; CREATE TABLE student ( stu_id INT UNSIGNED NOT NULL AUTO_INCREMENT, stu_no VARCHAR(20) NOT NULL, name VARCHAR(50) NOT NULL, major VARCHAR(100), PRIMARY KEY (stu_id), UNIQUE KEY uk_stu_no (stu_no) ) ENGINEInnoDB; CREATE TABLE course ( course_id INT UNSIGNED NOT NULL AUTO_INCREMENT, course_no VARCHAR(20) NOT NULL, title VARCHAR(100) NOT NULL, credit TINYINT UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (course_id) ) ENGINEInnoDB; CREATE TABLE sc ( stu_id INT UNSIGNED NOT NULL, course_id INT UNSIGNED NOT NULL, score DECIMAL(5,1), PRIMARY KEY (stu_id, course_id), CONSTRAINT fk_sc_student FOREIGN KEY (stu_id) REFERENCES student (stu_id), CONSTRAINT fk_sc_course FOREIGN KEY (course_id) REFERENCES course (course_id) ) ENGINEInnoDB; SET FOREIGN_KEY_CHECKS 1;这里有两个关键设计第一主键用自增的stu_id而业务编号stu_no单独加唯一约束。这样做是避免主键被业务影响你以后改学号规则不会引发外键链路变动。第二SET FOREIGN_KEY_CHECKS 0和 1包住建表语句避免因为建表顺序导致外键还没创建就引用不存在的父表。参数说明utf8mb4是四字节 UTF-8能存 emoji 和特殊符号如果你用默认utf8将来插入某些生僻字时会报Incorrect string valueENGINEInnoDB必须写MyISAM 不支持事务和外键实验五如果考事务MyISAM 会让你全部翻车DECIMAL(5,1)存储成绩精确到一位小数比FLOAT更适合拿来比较和统计。导入数据也分两步走。数据脚本如果报Cannot add or update a child row说明外键对应父记录不存在不是语法问题。此时先用SELECT COUNT(*)看父表行数再检查数据文件里有没有重复主键通常就能定位。4. 数据库增删改查与事务提交把应用层和数据库层接起来4.1 连接池与最小连接代码不要每次请求都 New Connection实验五如果包含应用层代码最常翻车的地方不是 SQL而是数据库连接管理。我见过很多同学在循环里pymysql.connect()程序跑完以后数据库服务直接卡死。原因不是数据库不行而是每一个连接都要经过 TCP 握手、认证、权限校验开销远大于 SQL 本身。先说最小连接代码能跑通。Python 里以 pymysql 为例import pymysql import pymysql.cursors conn pymysql.connect( host127.0.0.1, port3306, userlab5_user, passwordlab5_pass, databaselab5, charsetutf8mb4, cursorclasspymysql.cursors.DictCursor, autocommitFalse ) try: with conn.cursor() as cur: cur.execute(SELECT COUNT(*) AS total FROM student) row cur.fetchone() print(row[total]) finally: conn.close()这个代码块里的参数说明charsetutf8mb4必须和建库时的字符集一致否则中文写进去会乱码cursorclassDictCursor让 fetch 返回字典而不是元组可读性好很多autocommitFalse是实验五考事务时的关键后面会用到。finally里的conn.close()是最容易被遗忘的开发环境下忘了关连接暂时无事跑大量数据时就会爆掉。但真实课程验收不会只用这种方式建一次连接。代码层面要引入连接池概念不是让你自己写一个池而是理解“连接复用”。常见做法是用 DBUtils 的 PooledDBfrom dbutils.pooled_db import PooledDB pool PooledDB( creatorpymysql, maxconnections10, mincached2, maxcached5, blockingTrue, host127.0.0.1, port3306, userlab5_user, passwordlab5_pass, databaselab5, charsetutf8mb4 ) def get_conn(): return pool.connection()记一下maxconnections和blocking这两个参数maxconnections10是限制应用最多同时开 10 个连接blockingTrue表示池子里没有空闲连接时调用方等待而不是报错。连接池的意义不是把连接数无限放大而是让 10 个连接在 100 个并发请求之间周转。提交实验报告时把连接池配置截图放进去比单独写一句“我用了连接池”更有说服力。4.2 DAO 与参数化 SQL实验评分标准里的“增删改查解耦”数据库实验五的应用层评分点通常是增删改查也就是 CRUD。如果所有 SQL 都写在界面逻辑里代码会很难维护而且验收老师只要多问一句“我要改成 SQL Server 怎么办”整个程序就要重写。最常见的解法是 DAO 模式。DAO 不是框架只是一层约定每个数据库表对应一个数据访问类类里只写增删改查不写业务规则。以 student 表为例class StudentDAO: def __init__(self, conn): self.conn conn def insert(self, stu_no: str, name: str, major: str) - int: sql INSERT INTO student (stu_no, name, major) VALUES (%s, %s, %s) with self.conn.cursor() as cur: cur.execute(sql, (stu_no, name, major)) return cur.lastrowid def find_by_no(self, stu_no: str): sql SELECT stu_id, stu_no, name, major FROM student WHERE stu_no %s with self.conn.cursor() as cur: cur.execute(sql, (stu_no,)) return cur.fetchone() def update_major(self, stu_id: int, major: str): sql UPDATE student SET major %s WHERE stu_id %s with self.conn.cursor() as cur: cur.execute(sql, (major, stu_id)) return cur.rowcount def delete(self, stu_id: int): sql DELETE FROM student WHERE stu_id %s with self.conn.cursor() as cur: cur.execute(sql, (stu_id,)) return cur.rowcount这里有一个很关键的参数化写法的细节SQL 里的%s是占位符数据通过第二个参数元组传入而不是用字符串拼接。你看到的VALUES (%s, %s, %s)和cur.execute(sql, (stu_no, name, major))是一对它们把数据和 SQL 分开传。为什么必须这样因为字符串拼接很容易写出一句话“学生名叫 O‘Neil”的时候SQL 里的单引号会提前闭合轻则报语法错误重则变成注入点。数据库实验五不要求你练安全攻防但参数化 SQL 是任何数据库访问代码的基本底线。DAO 的好处还在于事务边界清楚。你要修改一个学生的专业同时要更新选课表里的某个信息DAO 的几个方法可以被同一个 connection 调用在业务层统一 commit 或 rollback。4.3 事务、锁和存储过程实验五最容易拿分的并发考点实验五到了进阶环节几乎必然会考事务。事务说白了就是“一组操作要么全成功要么全回滚”典型场景是转账A 扣钱和 B 加钱必须同时发生不能出现扣了钱没加上去。最少可用的事务写法是手动控制 begin、commit、rollbackdef transfer(from_id: int, to_id: int, amount: float): try: conn.begin() dao.deduct(from_id, amount) dao.add(to_id, amount) conn.commit() except Exception: conn.rollback() raise这段代码里conn.begin()之前实际上autocommitFalse已经起作用了。conn.begin()显式声明事务开始commit()把两行 update 的结果一起落盘。如果第二条 update 因为外键或者类型错误抛异常rollback()会把第一条 update 也撤销。这就是数据库的后悔药但前提是你没把autocommit设成 True。事务再往下走就是锁。两个用户同时改同一条学生记录数据库会加行锁一个事务长时间不提交另一个事务会一直等直到超时。实验五常见考题是解释死锁和解决方案。死锁的直接原因是两个事务各拿了一把锁又都想争对方手里的锁。解决办法主要有三条固定访问表的顺序、尽量缩短事务、两个事务不要跨越多个数据库。在代码里控制事务尽量短不要在一个事务里做查询、网络请求、文件 IO 这类和数据库无关的事情。存储过程和触发器实验五有时也会考。存储过程的价值不是“把 SQL 写到数据库里”而是把多条 SQL 封成一次调用减少应用和数据库之间的往返。如果指导书里明确要求写存储过程建议至少会一个参数输入 一个条件判断 一个事务控制的例子。5. 数据库实验五实践避坑记录连接、乱码、锁与资源泄漏排查5.1 MySQL 8 认证插件导致应用连接失败现象命令行mysql -u lab5_user -p能登录但 Python 或 Java 程序连接时报Authentication plugin caching_sha2_password cannot be loaded程序刚开始还能连后来突然连不上。原因MySQL 8 默认的认证插件是caching_sha2_password老版本的 pymysql 和 JDBC 驱动不认识。数据库服务没问题是驱动版本太老。解决升级驱动pymysql 升级到 1.0.2 以上JDBC 用mysql-connector-j8.x然后在连接参数里显式加useSSLfalseallowPublicKeyRetrievaltrue。如果你不想折腾驱动可以在 MySQL 里把用户改回mysql_native_password但那是教学环境的临时做法生产环境不建议。5.2 SQL 批处理因外键顺序执行失败现象一次性导入 schema.sql 和 data.sql报错Cannot add or update a child row: a foreign key constraint fails或者Table sc doesnt exist。原因脚本里先建了子表或者数据文件里先插了选课记录但父表还没创建/还没数据。这不是 SQL 语法错是执行顺序错。解决把SET FOREIGN_KEY_CHECKS0放到导入会话开头先建父表再建子表先插父表数据再插子表数据。导入完看错误日志里第一个报错的表从那张表往前查依赖关系。如果你没有改脚本的权限至少可以分多次导入schema 一次、data 一次。5.3 中文数据写入变成问号现象INSERT 语句里写中文select 出来是??或者程序插入后数据库里是乱码。原因三层字符集不一致。客户端连接用的字符集、数据库/表默认字符集、SQL 文件本身的编码三层里只要有一层是 latin1 或 utf8中文就可能丢。很多时候不是数据库配置坏了是 SQL 文件是 GBK 编码但数据库会话按 UTF-8 读。解决建库时统一用utf8mb4连接参数用charsetutf8mb4SQL 文件另存为带utf8编码。如果已经出现??数据已经丢了重新导入之前先把这三层统一。做实验前先把连接端SET NAMES utf8mb4;跑一遍能解决一半乱码问题。5.4 连接池耗尽与 SQL 没释放现象程序跑了几千条数据后突然变慢然后报Too many connections重启项目又能用了。原因连接没关闭。可能是conn.close()没执行也可能是连接池里的连接没归还。连接池很容易给人“一直开着就行”的错觉但连接池不等于无限连接它里面每个连接依旧是数据库会话。解决使用with conn:或者try/finally保证连接关闭/归还连接池参数maxconnections不要调太大一般 10 到 20 足够。排查方法是连到 MySQL 执行SHOW PROCESSLIST;看Sleep状态的连接是不是占满了。看到大量来自同一 IP 的Sleep基本就是代码里忘了关闭连接。5.5 数据库备份与同步只迁移数据不迁移结构现象你在实验机上导出.sql拿到自己电脑导入发现表存在但数据对不上或者存储过程、触发器全没了。原因很多图形客户端默认只导出数据不导出结构就算导出结构也不导出外键和索引。实验五的 zip 资源包如果允许你迁移环境最容易漏的就是索引和触发器。解决用命令行mysqldump导出时用--databases和--routinesmysqldump -u root -p --databases lab5 --routines --triggers --add-drop-table lab5_full.sql如果只是做日常同步用mysqldump的逻辑备份比直接复制数据目录更可控。数据库同步软件迁移大表数据很快但结构变更它们是检测不到的尤其实验过程中你可能改过字段注释、加过索引导出前先检查自己有没有把这些变更同步回实验包。6. 用 EXPLAIN、慢查询日志和并发脚本给实验五做一次体检6.1 用 EXPLAIN 判断索引是否真的命中实验五交之前我会用EXPLAIN把所有查询语句过一遍。下面是一个典型的两表 join 查询EXPLAIN SELECT s.name, c.title FROM student s JOIN sc ON s.stu_id sc.stu_id JOIN course c ON c.course_id sc.course_id WHERE s.stu_no 20240001\G看结果里几个字段type如果是ALL说明全表扫描这是扣分点如果看到eq_ref或者ref说明索引用上了。rows是 MySQL 预估扫的行数越小越好。Extra里出现Using filesort说明排序没走索引可以先不管但要知道它为什么出现。这个命令的价值是让你提交代码前就知道查询性能怎么样而不是等验收老师点开发现卡顿。6.2 用慢查询日志定位“跑得慢的不是 SQL 而是设计”打开慢查询日志设置一个很短的阈值比如 1 秒SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 1; SHOW VARIABLES LIKE slow_query_log%;然后用应用的功能把增删改查都操作一遍再去查慢查询日志。如果某条 SQL 频繁出现且执行时间超过 1 秒问题大概率不是数据库慢而是表结构设计少了索引或者关联查询把中间结果放大了。这两步能把“我觉得它跑得快”变成“日志证明它跑得快”。6.3 并发测试用多线程批处理模拟两个用户同时改同一条记录数据库实验五考并发时我不建议只写一段“理论上会死锁”的文字。可以写一个 Python 多线程脚本模拟两个线程同时对同一条成绩做UPDATE sc SET score score 1观察是否出现锁等待或者死锁回滚import threading def update_score(uid, cid, delta): conn pool.connection() try: conn.begin() with conn.cursor() as cur: cur.execute( UPDATE sc SET score score %s WHERE stu_id %s AND course_id %s, (delta, uid, cid) ) conn.commit() except Exception: conn.rollback() finally: conn.close() threads [ threading.Thread(targetupdate_score, args(1, 1, 1)), threading.Thread(targetupdate_score, args(1, 1, 2)), ] for t in threads: t.start() for t in threads: t.join()这段脚本如果正常跑完说明行锁没冲突如果有一个线程报Deadlock found说明两个事务拿锁顺序有问题。解决思路是让两个事务访问同一行的顺序保持一致避免交叉等待。我自己的习惯是每次改完表结构或者 SQL先用EXPLAIN看一遍再跑一次慢查询日志最后用并发脚本压一遍关键更新语句。这三张“体检报告”放在实验报告里比单纯写“功能正常”要扎实得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表