干了这么多年软件测试,最怕听到的一句话就是:“你把库里数据改一下,我验证下这个功能。”听起来是个极小的事,但如果你不懂数据库,连改哪张表、用 update 还是 insert、改完怎么确认都不会。可以说,软件测试工程师的日常工作中,数据库就像一把手术刀,不会用的时候觉得深不可测,真正掌握了才发现,测试要造数据、查问题、验结果,每一步都离不开它。这篇内容就是给测试同行准备的数据库知识体系,从最基础的表、字段、SQL 增删改查,到测试项目里的实战用法和面试高频场景,通通讲清楚。无论是刚入行的新人,还是做了几年想突破的进阶者,都能从中找到可以直接落地的操作思路。
不会让你去背一堆用不上的理论,我尽量用测试场景来讲数据库:为什么表结构会影响测试设计,怎么把一条 SQL 变成测试步骤,接口报错了怎么从库里面找原因。下面进入正题。
1. 软件测试工程师为什么绕不开数据库
1.1 测试工作里的数据库无处不在
很多人刚做测试时以为数据库是开发的事,自己只要会点点点就行了。但真正进入项目就会发现,功能测试要验证新增的数据是否落库,接口测试要确认返回值和库里的记录一致,自动化测试跑完要看测试数据有没有污染环境。哪怕是提一个 bug,开发都会反问你:“这条数据的创建时间是几点?你用的账号在库里是什么状态?”如果连查库都不会,问题排查效率会非常低。
我举一个最常见的场景:用户注册功能。你在页面上注册了一个账号,提示注册成功,但这只能说明前端流程没报错,数据库里到底有没有这条用户记录、状态字段是不是预期的“启用”、密码是不是加密存储,都需要去库里查一遍。这就是测试中的“数据校验”,也是数据库在测试里最基础的应用。
再比如测试环境的脏数据问题。测试人员经常在共享环境里跑用例,上次测试留下的数据很可能影响下次执行。这时候就需要你会用 delete 清理数据,或者会用 update 把数据恢复到初始状态。不会数据库,就只能求开发帮忙,或者傻傻地换个账号重跑,效率低不说,还容易踩坑。
1.2 测试角色需要的数据库能力层级
数据库知识在测试工作中的深浅,和你的职级、工作内容是强相关的。不是要求所有人都成为 DBA,但至少要按需掌握。我自己习惯把测试人员的数据库能力分成三层:
| 层级 | 能力要求 | 典型工作场景 |
|---|---|---|
| 初级 | 能看懂 SQL,会基本的 SELECT 查询,知道表、字段、主键的概念 | 根据需求查测试数据、确认数据落库、排查简单问题 |
| 中级 | 熟练增删改查,掌握常用函数、多表联查、子查询,能安全地修改测试环境数据 | 构造测试数据、清理脏数据、配合接口测试做数据断言 |
| 高级 | 理解事务、锁、索引、连接池,能分析死锁和性能问题,了解常见数据库差异 | 并发测试、性能测试、数据迁移验证、测试环境数据库架构设计 |
刚开始做测试的同学,不用被“高级能力”吓到。数据库是越用越熟的,先把自己手头最频繁用的查询练到不看文档也能写,再慢慢往事务、锁这些方向挖。
记得我有一次面试一个三年经验的测试,问他“一条 SQL 查询很慢,你会怎么排查”,他只回了一句“可以加索引”。但继续问“加索引一定会变快吗?在什么样的查询条件下会让索引失效”,他就答不上来了。这说明他的数据库知识还停留在“听过概念”的阶段。测试工程师掌握数据库,不是为了背八股文,而是为了在实际问题中拿起来就能用。
2. 数据库核心概念:先建好自己的知识框架
2.1 从一张学生表说起:表、字段、记录、主键
数据库最核心的模型其实就是一张张二维表格。拿一张学生表举例:
| 学号 | 姓名 | 班级 | 成绩 |
|---|---|---|---|
| 1001 | 张三 | 1班 | 88 |
| 1002 | 李四 | 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 个用户,或者查看某笔订单最新的状态记录。
另外还有一个高频操作:统计数量。想验证“新增了多少条测试数据”,直接用 COUNT:
SELECT 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( host='192.168.1.100', user='test', password='test123', database='shop', charset='utf8mb4' ) 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(host='127.0.0.1', user='root', password='123456', database='test') 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_PASSWORD=123456 mysql:8.4容器化数据库对测试工程师来说是个好东西。以后遇到“需要某版本的数据库验证场景”,不需要求 DBA 给环境,自己用 Docker 起一个,用完直接删掉,非常干净。如果你所在项目用的是国产数据库,比如人大金仓,也可以用 Docker 镜像在测试环境做功能兼容性验证。这个技能已经越来越像测试工程师的标配了。
写在最后的一点体会
数据库这块内容,带过很多人之后,我最大的体会是:千万不要一开始就钻到高深的理论里,先从你项目里的真实表结构入手。拿到一份测试环境库,先去看看用户表、订单表、商品表长什么样,再试着用几条 SELECT 把核心业务数据串起来。等你发现自己能回答“数据为什么没落库”“接口调用后库里变化是否符合预期”这些问题时,数据库就不再是测试路上的拦路虎了。
最后再分享一个我自己的小技巧:每次接到一个新测试任务,先不要急着点点点,花 5 分钟想一想“这个功能会操作哪些表?哪些字段会变?有什么唯一约束?”想清楚之后,再去做测试设计和数据准备,你的用例会比以前扎实很多。这个习惯帮我发现了不少别人漏掉的 bug,希望你也能用上。