
干了这么多年软件测试最怕听到的一句话就是“你把库里数据改一下我验证下这个功能。”听起来是个极小的事但如果你不懂数据库连改哪张表、用 update 还是 insert、改完怎么确认都不会。可以说软件测试工程师的日常工作中数据库就像一把手术刀不会用的时候觉得深不可测真正掌握了才发现测试要造数据、查问题、验结果每一步都离不开它。这篇内容就是给测试同行准备的数据库知识体系从最基础的表、字段、SQL 增删改查到测试项目里的实战用法和面试高频场景通通讲清楚。无论是刚入行的新人还是做了几年想突破的进阶者都能从中找到可以直接落地的操作思路。不会让你去背一堆用不上的理论我尽量用测试场景来讲数据库为什么表结构会影响测试设计怎么把一条 SQL 变成测试步骤接口报错了怎么从库里面找原因。下面进入正题。1. 软件测试工程师为什么绕不开数据库1.1 测试工作里的数据库无处不在很多人刚做测试时以为数据库是开发的事自己只要会点点点就行了。但真正进入项目就会发现功能测试要验证新增的数据是否落库接口测试要确认返回值和库里的记录一致自动化测试跑完要看测试数据有没有污染环境。哪怕是提一个 bug开发都会反问你“这条数据的创建时间是几点你用的账号在库里是什么状态”如果连查库都不会问题排查效率会非常低。我举一个最常见的场景用户注册功能。你在页面上注册了一个账号提示注册成功但这只能说明前端流程没报错数据库里到底有没有这条用户记录、状态字段是不是预期的“启用”、密码是不是加密存储都需要去库里查一遍。这就是测试中的“数据校验”也是数据库在测试里最基础的应用。再比如测试环境的脏数据问题。测试人员经常在共享环境里跑用例上次测试留下的数据很可能影响下次执行。这时候就需要你会用 delete 清理数据或者会用 update 把数据恢复到初始状态。不会数据库就只能求开发帮忙或者傻傻地换个账号重跑效率低不说还容易踩坑。1.2 测试角色需要的数据库能力层级数据库知识在测试工作中的深浅和你的职级、工作内容是强相关的。不是要求所有人都成为 DBA但至少要按需掌握。我自己习惯把测试人员的数据库能力分成三层层级能力要求典型工作场景初级能看懂 SQL会基本的 SELECT 查询知道表、字段、主键的概念根据需求查测试数据、确认数据落库、排查简单问题中级熟练增删改查掌握常用函数、多表联查、子查询能安全地修改测试环境数据构造测试数据、清理脏数据、配合接口测试做数据断言高级理解事务、锁、索引、连接池能分析死锁和性能问题了解常见数据库差异并发测试、性能测试、数据迁移验证、测试环境数据库架构设计刚开始做测试的同学不用被“高级能力”吓到。数据库是越用越熟的先把自己手头最频繁用的查询练到不看文档也能写再慢慢往事务、锁这些方向挖。记得我有一次面试一个三年经验的测试问他“一条 SQL 查询很慢你会怎么排查”他只回了一句“可以加索引”。但继续问“加索引一定会变快吗在什么样的查询条件下会让索引失效”他就答不上来了。这说明他的数据库知识还停留在“听过概念”的阶段。测试工程师掌握数据库不是为了背八股文而是为了在实际问题中拿起来就能用。2. 数据库核心概念先建好自己的知识框架2.1 从一张学生表说起表、字段、记录、主键数据库最核心的模型其实就是一张张二维表格。拿一张学生表举例学号姓名班级成绩1001张三1班881002李四2班76这张表里每一列叫“字段”也叫列名。比如“学号”就是一个字段“姓名”也是一个字段。每一行数据叫“记录”也叫行。整个表的结构叫“表结构”定义这个结构的过程叫建表。这里最关键的概念是主键。主键是唯一标识一条记录的字段讲究的是一个“唯一”。在刚才的学生表里学号就是主键因为一个班级里不可能有两个学号相同的学生。但姓名就不适合做主键因为可能会有两个人都叫张三。测试工程师设计测试数据时一定要留意主键你在连续造数据时如果主键重复插入就会失败这就是唯一性约束在起作用。记住一个基本规则别以为主键只是开发的事。当你做数据库增删改查、准备测试数据时主键永远是第一道关。不知道哪一列是主键就没法安全地定位某一条记录。2.2 关系型数据库 vs 非关系型数据库测试项目里最常遇到的数据库大概分两类关系型和非关系型。关系型数据库代表有 MySQL、PostgreSQL、Oracle、SQL Server 等。它们的特点是数据按照表结构存储表与表之间可以建立关联关系比如订单表通过用户 ID 关联用户表。这类数据库用 SQL 操作支持事务数据一致性强。目前绝大多数业务系统尤其是金融、电商、后台管理系统都是关系型数据库为主。非关系型数据库也叫 NoSQL常见有 MongoDB、Redis、Cassandra 等。它们的存储模型更灵活比如 MongoDB 里数据像 JSON 文档一样存在集合里Redis 则是 key-value 类型。这类数据库通常追求高性能、高扩展性但数据一致性策略和关系型不太一样。那测试工程师要都学吗我的建议是至少把一种关系型数据库学透比如 MySQL。因为关系型数据库用得最广面试也最容易问。非关系型数据库等你进了项目再按需学习有 MySQL 的底子在切换过去的成本并不高。特别是做接口测试时你常常会遇到“接口存到 Redis 的缓存数据怎么验证”的问题这时候再针对 Redis 的命令做专项学习就行。2.3 约束、索引、事务这些概念到底在测试中怎么用有些概念看起来理论性很强比如主键、唯一约束、非空约束、外键约束到索引、事务、锁。但在实际测试中它们都有非常具体的应用场景。约束就是限制表中数据的规则。非空约束表示这一列必须有值唯一约束表示这一列不能重复外键约束表示某一列的值必须在关联表中存在。测试人员设计数据时要主动想到这些规则。比如测试一个“创建商品”的功能商品编码字段如果设了唯一约束那你就需要设计两条相同编码的数据验证系统会不会给出“编码重复”的友好提示。不理解约束你根本不会想到这个测试点。索引是提高查询速度的机制类似一本书的目录。测试人员遇到查询慢的时候经常会想到加索引。但索引也是有成本的它会占用存储空间也会拖慢写入速度。所以“加索引一定能解决问题”是错误观念还需要看查询条件是否命中索引。事务可以理解成一组要么全部成功、要么全部失败的操作。经典的银行转账就是事务A 账户扣款、B 账户加款这两个操作必须同时成功不能只成功一半。测试人员在造数据时可以利用事务的回滚能力把操作临时性地插入到库里验证后再回滚这样不会给测试环境留下脏数据。3. 从入门到熟练SQL增删改查的测试视角3.1 查询是测试验证的主力SELECT 的常见姿势SELECT 是测试人员用得最多的 SQL没有之一。它的作用就是从数据库里把数据查出来。最基础的方式是查全表SELECT * FROM user;但实际工作中千万别动不动就SELECT *因为测试环境的数据可能几十万上百万条把所有字段全部查出来既慢又浪费资源还会把无关字段混进结果里影响判断。建议把字段名写清楚SELECT id, username, status, created_at FROM user WHERE status 1;这条语句里 WHERE 是过滤条件相当于只挑出状态为 1 的用户。如果只想看前 10 条用 LIMIT 限制SELECT id, username FROM user ORDER BY created_at DESC LIMIT 10;ORDER BY 是按某个字段排序DESC 是倒序ASC 是正序默认是正序。这个组合在测试中特别常用比如查看最新注册的 10 个用户或者查看某笔订单最新的状态记录。另外还有一个高频操作统计数量。想验证“新增了多少条测试数据”直接用 COUNTSELECT COUNT(*) FROM order_info WHERE user_id 1001;拿到计数后你可以和业务逻辑里的预期数量对比。比如分页接口第一页显示 20 条但库里满足条件的有 25 条说明第二页应该还有 5 条这就是接口测试里对分页逻辑的校验。3.2 测试数据准备全靠 INSERT测试过程中最常做的事之一就是造数据。比如你要测试一个订单列表功能但数据库里当前用户没有订单这时候就需要插入几条模拟订单。插入单条数据的写法INSERT INTO user (username, password, status, created_at) VALUES (test_user_001, e10adc3949ba59abbe56e057f20f883e, 1, NOW());注意字段名和值要一一对应字符串类型的值要用单引号括起来日期类型可以用 NOW() 函数表示当前时间。如果插入多条数据可以一次性写多个值INSERT INTO user (username, password, status, created_at) VALUES (test_user_002, e10adc3949ba59abbe56e057f20f883e, 1, NOW()), (test_user_003, e10adc3949ba59abbe56e057f20f883e, 0, NOW());这条语句执行后就会一次性插入两个用户。测试环境造数时尽量用简单明确的标识比如test_user_001这样之后清理数据时你可以用 LIKE 条件把它们一网打尽DELETE FROM user WHERE username LIKE test_user_%;这个技巧我每次都会强调造数前先想好怎么清理。很多测试环境脏数据多就是因为插入数据时没有统一的标识规则等到清理时根本不知道哪些是自己造的。3.3 数据修改和清理UPDATE/DELETE 的危险与救赎UPDATE 是修改数据DELETE 是删除数据。这两个操作在测试环境里非常危险因为一旦条件写错影响范围可能远超预期。最常见的翻车现场是忘记写 WHERE 条件UPDATE user SET status 0;这一句会把 user 表里所有用户的 status 都改成 0而且没有后悔药。如果是在生产环境执行那就是事故。所以在测试环境操作 UPDATE 之前我的习惯是先用同条件的 SELECT 查一遍确认影响范围SELECT id, username, status FROM user WHERE username test_user_001; -- 确认只有这条数据后再执行 UPDATE user SET status 0 WHERE username test_user_001;DELETE 同理删除前先用 SELECT 确认删除时尽量用主键或唯一字段定位。另外DELETE 删除的是数据记录但表结构还在。如果你需要清空一张表并重置自增主键才会用到 TRUNCATE但这条语句不能按条件删整张表的数据全部清空使用前必须和团队确认。还有一点修改和删除操作最好放到一个事务里执行比如 MySQL 里先BEGIN执行完操作后SELECT验证确认没问题再COMMIT提交如果有问题就ROLLBACK回滚。这样能避免手误给测试环境留下不可恢复的破坏。4. 测试项目实战怎么用数据库知识解决实际测试问题4.1 造数据和数据校验从需求到 SQL 落地拿一个登录功能来举例。你要测试“用户名不存在时的提示”正常流程是先注册一个账号然后登录。但如果注册流程还没开发完怎么验证登录功能这时候就需要直接往数据库里插入一条用户记录再去页面上登录。这就是为什么测试工程师必须会造数。实操步骤大概是这样的看用户表结构确认必填字段。比如 username、password、status、role_id。用 INSERT 插入一条符合业务规则的数据。密码字段如果是 MD5 加密就需要存入加密后的值不能存明文。去登录页面输入这条数据的用户名和密码验证登录是否成功。测试结束后用 DELETE 删除这条测试数据。造数最重要的是贴近真实业务。比如注册功能要求用户名唯一那么造数时就要设计不同的用户名组合覆盖正常、重复、超长、含特殊字符等情况。数据库知识在这里的作用有两层一层是能造出数据另一层是能理解业务对数据的限制从而设计出更全面的测试用例。4.2 接口测试和数据库断言的配合接口测试里只判断 HTTP 状态码和返回参数是远远不够的。接口返回 200可能只是接口内部逻辑没报错但数据库里该更新的字段也许根本没更新。所以现在很多测试团队做接口自动化时都会加上“数据库断言”这一层。举个例子测试一个“修改用户昵称”的接口。调用接口后除了看返回结构还要去用户表里查出最新的昵称字段确认改动真的落库了。用 Python 写一个简单的验证逻辑可能是这样的import pymysql def get_user_nickname(user_id): # 连接测试环境数据库 conn pymysql.connect( host192.168.1.100, usertest, passwordtest123, databaseshop, charsetutf8mb4 ) cursor conn.cursor() cursor.execute(SELECT nickname FROM user WHERE id %s, (user_id,)) row cursor.fetchone() cursor.close() conn.close() return row[0] if row else None接口调用完以后调用这个函数去拿到数据库里的昵称再和接口请求里的新昵称做比较。如果两者不一致说明接口虽然返回成功但底层数据并没有更新这个 bug 就漏不掉。数据库断言是接口测试里含金量很高的技能面试官问你“接口返回正常你怎么确认数据真没问题”其实就是在考察你这层能力。4.3 涉及物联网设备的软件测试怎么测数据库能帮上什么忙物联网设备测试最近问的人特别多比如智能水表、温度传感器、车联网盒子。这类设备和传统 Web 系统的最大区别是数据产生频率极高设备每隔几秒甚至几百毫秒就会上报一次数据。这些数据很多会写入时序数据库比如 TDengine或者以固定频率写入关系型数据库。测试物联网设备时数据库层面的关注点通常包括数据上报完整性。设备上报了 1000 条数据数据库里是否真的存了 1000 条中途网络断了恢复后有没有补传机制数据准确性。设备上报的温度是 25.5 度库里存的会不会是 25.4999 或者出现单位转换错误数据时序性。上报时间是不是按顺序排列有没有乱序的数据数据重复性。设备重复上报了同一条数据数据库是去重还是重复存储这往往是 bug 高发区。验证这些场景靠人工点点点根本不可能必须用 SQL 查询、聚合统计、以及脚本比对。比如你想查一段时间内某设备上报了多少条数据可以写SELECT device_id, COUNT(*) FROM sensor_data WHERE report_time BETWEEN 2025-01-01 00:00:00 AND 2025-01-01 01:00:00 GROUP BY device_id;GROUP BY 是按设备分组统计这样就能快速发现数据缺失或突然暴增的异常情况。物联网项目的测试工程师数据库知识不只是在 MySQL 里查业务数据还要了解时序数据库的基本操作核心道理是相通的用数据说话。5. 测试工作中常见数据库问题与排查实录5.1 数据库死锁和并发锁为什么会卡住做过并发测试的同学一定遇到过类似报错Deadlock found when trying to get lock; try restarting transaction。这就是数据库死锁。简单理解两个事务各自持有一把锁然后都在等待对方释放锁结果互相僵持谁也继续不了。用一个例子说明事务 A 先更新订单表再更新用户表事务 B 先更新用户表再更新订单表。如果两个事务同时执行A 拿到订单表的锁B 拿到用户表的锁然后 A 想继续拿用户表的锁但被 B 占着B 想继续拿订单表的锁但被 A 占着双方就死锁了。测试人员在做并发测试时如果发现死锁不要只知道每次都复现不了。要判断问题是不是出在锁顺序上。可以在事务里固定更新顺序比如所有操作都先更新用户表再更新订单表这样能显著减少死锁概率。另外如果一个事务里操作的数据量非常大锁持有时间就会变长加大死锁概率可以考虑把大事务拆成小事务。数据库锁还有一个常见问题是“锁等待超时”。测试环境碰到更新一条数据时一直卡住往往是有另一个事务没提交把这条记录锁住了。排查方法也很简单查看当前有哪些事务占用了锁找到对应的会话或程序确认是正常操作还是忘记提交的事务。-- MySQL 查看当前正在运行的事务 SELECT * FROM information_schema.innodb_trx;这条 SQL 能列出事务 ID、状态、执行时间等信息非常实用。在测试环境里我经常用这条语句定位那些被卡住的任务然后把长事务对应的会话结束掉环境就恢复了。5.2 MySQL 连接池配置不当导致的测试环境故障还有一类问题特别容易在测试环境出现to many connections。测试环境本身部署了多个服务如果数据库连接池参数配置得太小比如最大连接数只有 10而同时跑自动化测试的进程有 20 个连接可能瞬间被打满应用就会报错。连接池可以理解成一组预先建好的数据库连接应用使用完以后归还给池子下次再复用。它的核心参数包括最大连接数、最小连接数、连接超时时间等。如果最大连接数设得太小高并发时连接不够用设得太大数据库本身又扛不住。排查这个问题的思路是确认报错信息看是不是连接数相关的错误。用SHOW VARIABLES LIKE max_connections;查看数据库允许的最大连接数。用SHOW PROCESSLIST;查看当前正在使用的连接。和开发确认应用连接池配置是否符合当前并发压测规模。测试人员要明白连接池报错不一定是程序 bug可能是测试环境配置问题。比如你用 JMeter 开 100 个线程跑接口如果应用连接池最大连接数是 50那很大概率会出现连接等待或拒绝。压测前先评估连接池规格是被很多人忽略但又很关键的一步。5.3 面试高频场景给一个测试需求你怎么设计数据现在面试里很少直接问“你会不会写 SQL”而是喜欢给一个场景看你怎么组织思路。比如面试官说“我想测购物车修改商品数量的功能你会怎么准备测试数据和验证结果”这个问题看似开放但核心是考察三点SQL 功底、业务理解、数据闭环。比较好的回答思路是先分析购物车表结构找出关键字段用户 ID、商品 ID、数量、更新时间。准备几种场景的数据正常场景购物车里有一条数量为 1 的记录改成 3验证更新成功。边界场景商品数量改成 0观察是删除记录还是允许 0。异常场景购物车里该商品不存在直接修改数量看接口怎么处理。并发场景两个请求同时修改同一件商品数量看最终数据库里的值是否合理。每个场景执行后用 SELECT 查询购物车表断言实际数量和预期一致。测试结束用 DELETE 清理数据。你看这样的回答不仅展示了数据库增删改查能力还体现了测试设计的层次感。面试官真正想看到的是你能否把数据库当成测试设计的一部分而不是停留在“会写 hello world 级别的 SQL”。6. 常用工具和效率技巧6.1 图形化工具和命令行怎么选数据库图形化工具很多常用的有 DBeaver、Navicat、MySQL Workbench。DBeaver 是开源的免费且支持多种数据库比如 MySQL、PostgreSQL、Oracle、SQLite非常合适测试团队使用。Navicat 很多公司有授权操作体验也不错但它是收费的。我的建议是优先用 DBeaver省得为版权问题纠结如果公司已经买了 Navicat 的 license那直接用 Navicat 也没问题。图形化工具的好处是直观表结构、数据内容一目了然。但我也建议每个测试工程师掌握命令行客户端的基本用法比如 MySQL 的命令行mysql -h 192.168.1.100 -u test -p为什么还要学命令行因为编写自动化测试脚本时你往往是在代码里连接数据库没有图形界面只能靠命令行或客户端库。而且命令行执行 SQL 脚本非常方便你可以把一串造数、清数据的 SQL 写成一个 .sql 文件用命令批量执行mysql -h host -u user -p database init_data.sql这和图形化工具的操作形成互补。图形化适合快速查看、临时修改命令行适合批量操作、脚本化执行。两边都熟才是真正的高效。6.2 批量造数、Excel 导入和脚本化操作测试过程中经常需要大量数据比如测试分页功能需要 1000 条记录、压测前需要 10000 个用户。这时用手工一条条 INSERT 显然不现实。常见的高效做法有这么几种第一种是写 Python 脚本结合 Faker 库批量造数。Faker 可以生成姓名、手机号、地址、邮箱等假数据配合 pymysql 循环插入几分钟就能造出一万条。import pymysql from faker import Faker fake Faker(zh_CN) conn pymysql.connect(host127.0.0.1, userroot, password123456, databasetest) cursor conn.cursor() for i in range(1000): username fake.user_name() str(i) mobile fake.phone_number() cursor.execute( INSERT INTO user (username, mobile) VALUES (%s, %s), (username, mobile) ) conn.commit() cursor.close() conn.close()第二种是使用 Excel 导入。很多图形化工具支持把 Excel 文件导入成数据库表或者你先把数据整理到 Excel再用工具批量生成 SQL。适合数据量不大、且数据结构比较简单的场景。第三种是利用存储过程在数据库内部循环插入。存储过程写法比较古老性能也不一定好但在纯数据库环境下很直接。只不过团队里如果其他成员不熟悉存储过程维护起来会费劲我一般只在数据量特别大时考虑它。批量造数最重要的是验证数据可用性。造完数不是就完事了还要抽几条查出来看看字段内容是否符合业务规则、关联表有没有对应数据。不然你造了一堆数据等到执行用例时发现外键对不上那才叫白忙活。6.3 测试环境数据库的容器化实践现在很多团队的测试环境会用 Docker 搭建数据库实例既可以在自己的电脑上快速跑一个 MySQL也可以模拟不同版本的数据库做兼容性测试。比如你想在本地拉一个 MySQL 8.4 的测试实例只需要一行命令docker run -d --name mysql-test -p 3306:3306 -e MYSQL_ROOT_PASSWORD123456 mysql:8.4容器化数据库对测试工程师来说是个好东西。以后遇到“需要某版本的数据库验证场景”不需要求 DBA 给环境自己用 Docker 起一个用完直接删掉非常干净。如果你所在项目用的是国产数据库比如人大金仓也可以用 Docker 镜像在测试环境做功能兼容性验证。这个技能已经越来越像测试工程师的标配了。写在最后的一点体会数据库这块内容带过很多人之后我最大的体会是千万不要一开始就钻到高深的理论里先从你项目里的真实表结构入手。拿到一份测试环境库先去看看用户表、订单表、商品表长什么样再试着用几条 SELECT 把核心业务数据串起来。等你发现自己能回答“数据为什么没落库”“接口调用后库里变化是否符合预期”这些问题时数据库就不再是测试路上的拦路虎了。最后再分享一个我自己的小技巧每次接到一个新测试任务先不要急着点点点花 5 分钟想一想“这个功能会操作哪些表哪些字段会变有什么唯一约束”想清楚之后再去做测试设计和数据准备你的用例会比以前扎实很多。这个习惯帮我发现了不少别人漏掉的 bug希望你也能用上。