ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Oracle/PL/SQL实战入门:从环境搭建到存储过程与性能优化

2026/8/26 7:25:48 拓冰建站 浏览量
Oracle/PL/SQL实战入门:从环境搭建到存储过程与性能优化 1. 项目概述为什么我们需要系统性地掌握Oracle/PL/SQL基础在数据驱动的世界里无论你是刚入行的开发新人还是需要与数据库打交道的业务分析师甚至是负责系统运维的工程师Oracle数据库都是一个绕不开的名字。它以其强大的事务处理能力、高度的稳定性和安全性长期占据着企业级数据库市场的重要份额。而PL/SQL作为Oracle数据库专属的过程化编程语言是将数据库操作从简单的“增删改查”提升到构建复杂业务逻辑的关键桥梁。我见过太多项目初期因为对基础操作掌握不牢导致后期性能瓶颈、数据混乱甚至安全漏洞频发维护成本呈指数级增长。这个系列内容就是针对这个痛点而来。它不是一本面面俱到的官方手册而是一份来自一线实战的“生存指南”。我们将避开那些晦涩难懂的理论堆砌直接聚焦于你日常工作中最高频、最核心的操作场景。从最基础的连接数据库、编写第一条SELECT语句到使用PL/SQL构建存储过程、处理异常再到理解那些让新手头疼的“ORA-”错误码背后的真相。我的目标是让你在阅读和实践之后不仅能完成工作更能理解每一个操作背后的“为什么”从而具备独立排查问题和优化代码的能力。无论你是准备面试还是正在啃一个棘手的数据库课程设计或是需要快速上手公司遗留的Oracle系统这里的内容都将为你提供一个坚实、可靠的起点。2. 核心操作环境搭建与连接配置2.1 数据库客户端工具选型与安装避坑工欲善其事必先利其器。连接和操作Oracle数据库首先得选对客户端工具。对于大多数开发者而言PL/SQL Developer和Oracle SQL Developer是两大主流选择。PL/SQL Developer以其极致的速度和丰富的功能如代码美化、调试器、会话监控深受资深DBA和开发者的喜爱。但在安装时有几个坑一定要避开。首先关于网络上流传的“pl sql 64 15 注册码”或“pl/sql develope破解”等关键词我必须强调使用未经授权的软件不仅存在法律风险更可能携带恶意代码危害公司数据安全。务必从官方或可信渠道获取正版授权。其次PL/SQL Developer只是一个客户端它需要依赖Oracle的即时客户端Instant Client才能连接数据库。很多连接失败的问题根源就在于即时客户端的版本与数据库服务器版本不兼容或者tnsnames.ora文件配置有误。Oracle SQL Developer是Oracle官方提供的免费图形化工具功能全面且跨平台。它的优势在于与Oracle数据库版本同步更新兼容性最好并且内置了数据建模、迁移如使用SSMA for Oracle工具迁移到其他数据库等多种高级功能。对于新手我通常推荐从SQL Developer开始可以减少很多环境配置的麻烦。下载时请认准Oracle官网避免从第三方站点下载到被篡改的安装包。另一个常被搜索的工具是“dbx数据库工具”这可能是一些特定场景下的工具或是对某个工具的别称。在Oracle生态中专注于数据库开发与管理的主流工具就是上述两者。选择的原则是追求极致效率和深度调试选PL/SQL Developer希望免配置、功能全面且免费选Oracle SQL Developer。2.2 连接配置详解与“ORA-28547”错误根治配置连接是入门的第一道关卡这里以最经典的通过tnsnames.ora文件配置为例带你彻底搞懂连接字符串。首先你需要从数据库管理员那里获取连接信息通常包括主机名或IP地址、端口号默认1521、服务名或SID。假设信息如下主机192.168.1.100端口1521服务名ORCLPDB。接下来找到你的Oracle客户端安装目录下的network/admin/tnsnames.ora文件。用文本编辑器打开添加一个网络服务名Net Service Name这个名称你可以自定义用于在客户端标识这个连接。ORCL_TEST (DESCRIPTION (ADDRESS (PROTOCOL TCP)(HOST 192.168.1.100)(PORT 1521)) (CONNECT_DATA (SERVER DEDICATED) (SERVICE_NAME ORCLPDB) -- 使用服务名 # 或者使用 SID ORCL ) )关键点解析ORCL_TEST这是你自定义的别名在PL/SQL Developer或SQL Developer中连接时就使用这个名字。SERVICE_NAMEvsSID在Oracle 12c及以上的多租户环境中通常使用SERVICE_NAME连接可插拔数据库PDB。如果是传统的单实例数据库则可能使用SID。务必向管理员确认清楚填错是连不上的。SERVER DEDICATED表示专用服务器模式适用于生产环境。对于开发测试也可以用SHARED共享服务器模式但专用模式连接更稳定。配置好后在工具中选择连接类型为“TNS”然后选择ORCL_TEST输入正确的用户名和密码即可连接。经典错误“ORA-28547: connection to server failed, probable oracle net admin error”排查这个错误几乎总是网络配置问题。请按以下顺序排查检查tnsnames.ora文件语法括号是否配对是否有中文标点建议用简单的文本编辑器如Notepad检查。检查网络连通性在命令行用tnsping ORCL_TEST命令测试。如果返回“TNS-12541: TNS:no listener”说明客户端根本找不到监听器问题在服务器端监听器未启动或网络不通。检查客户端与服务器版本兼容性尤其是使用即时客户端时确保版本不要相差太大。32位客户端连64位数据库一般没问题但反过来可能不行。检查防火墙确保客户端和服务器之间的1521端口是开放的。检查环境变量TNS_ADMIN环境变量是否指向了正确的tnsnames.ora文件所在目录这是最容易被忽略的一点。注意修改tnsnames.ora后无需重启任何服务客户端工具在下次连接时会自动读取。如果使用SQL Developer它也支持直接使用“高级”模式填写主机、端口、服务名而无需配置TNS这对新手更友好。3. SQL基础操作超越简单的增删改查3.1 数据查询的艺术与DUAL表的妙用查询是数据库操作中最频繁的部分。基础的SELECT * FROM employees;谁都会写但写出高效、准确的查询才是关键。精准查询与函数使用-- 避免使用SELECT *明确列出所需字段 SELECT employee_id, first_name, last_name, salary FROM employees WHERE department_id 50 AND hire_date DATE 2020-01-01 ORDER BY salary DESC; -- 使用聚合函数进行统计如查询总金额 SELECT SUM(order_total) AS total_sales, AVG(order_total) AS avg_sales FROM orders WHERE order_date BETWEEN DATE 2023-01-01 AND DATE 2023-12-31;这里提到了“oracle查询总金额”核心就是SUM聚合函数配合WHERE条件过滤。GROUP BY子句则用于分组统计。DUAL表一个特殊的工具表DUAL是Oracle中的一个虚拟表它只有一行一列常用于执行函数计算、获取系统信息等不需要从实际表中取数据的操作。-- 获取当前系统日期和时间 SELECT SYSDATE FROM DUAL; -- 进行算术或函数计算 SELECT 3.14159 * 10 * 10 AS area FROM DUAL; -- 调用序列获取下一个值虽然NEXTVAL通常不这样用但语法可行 -- SELECT my_sequence.NEXTVAL FROM DUAL;关于“oracle中dual最多存多大”这个问题本身是个误解。DUAL表的结构是固定的单行单列列名为DUMMY值为‘X‘它不存储用户数据因此不存在“存储多大”的概念。它的存在纯粹是为了满足SELECT语句的语法要求必须有FROM子句。日期处理函数TRUNC的深度解析TRUNC(sysdate)是日期处理中的瑞士军刀。它用于截断日期的时间部分。-- 返回当天日期时间部分为 00:00:00 SELECT TRUNC(SYSDATE) FROM DUAL; -- 例如2023-10-27 -- 按月份截断返回当月第一天 SELECT TRUNC(SYSDATE, MM) FROM DUAL; -- 例如2023-10-01 -- 按年份截断返回当年第一天 SELECT TRUNC(SYSDATE, YYYY) FROM DUAL; -- 例如2023-01-01这在按天、月、年进行数据分组统计时极其有用可以避免因时间部分不同而导致的分组错误。3.2 数据操纵与事务控制实战插入、更新、删除操作看似简单但结合事务控制才是保证数据完整性的关键。插入数据-- 基础插入 INSERT INTO employees (employee_id, first_name, last_name, email, hire_date, job_id) VALUES (1000, 张, 三, zhangsanexample.com, SYSDATE, IT_PROG); -- 从其他表复制数据 INSERT INTO employees_archive SELECT * FROM employees WHERE hire_date DATE 2010-01-01;更新数据务必带上WHERE条件否则会更新全表这是极其危险的操作。UPDATE employees SET salary salary * 1.1, -- 涨薪10% last_update_date SYSDATE WHERE department_id 60 AND performance_rating A;删除数据同样WHERE子句是生命线。对于重要数据建议先使用SELECT语句确认要删除的记录再执行DELETE。-- 先确认 SELECT * FROM log_table WHERE created_date ADD_MONTHS(SYSDATE, -12); -- 再删除删除一年前的日志 DELETE FROM log_table WHERE created_date ADD_MONTHS(SYSDATE, -12);事务控制Oracle中一个DML语句INSERT, UPDATE, DELETE执行后变化处于未提交状态。你需要显式地提交或回滚。-- 场景转账操作 UPDATE accounts SET balance balance - 1000 WHERE account_id A001; UPDATE accounts SET balance balance 1000 WHERE account_id A002; -- 此时如果第二条更新失败整个转账应该撤销 COMMIT; -- 两条更新都成功提交事务 -- 或者 ROLLBACK; -- 任何一条失败回滚所有更改养成在工具中手动执行COMMIT的习惯尤其是在生产环境。默认的自动提交设置可能隐藏问题。3.3 分页查询与ROWID的深入理解高效分页查询在Web应用中分页是刚需。Oracle 12c之前通常使用ROWNUM或分析函数ROW_NUMBER()实现。-- 使用ROWNUMOracle传统方式查询第6-10条记录 SELECT * FROM (SELECT t.*, ROWNUM AS rn FROM (SELECT * FROM employees ORDER BY hire_date DESC) t WHERE ROWNUM 10) WHERE rn 6; -- 使用OFFSET-FETCHOracle 12c及以上推荐更简洁 SELECT employee_id, first_name, hire_date FROM employees ORDER BY hire_date DESC OFFSET 5 ROWS FETCH NEXT 5 ROWS ONLY;OFFSET-FETCH语法更符合SQL标准可读性也更好是“oracle分页”的首选方案如果你的数据库版本支持。ROWID数据行的物理地址“oracle 的 rowid是什么”ROWID是Oracle数据库中每一行数据的唯一物理地址标识符它包含了该行数据在磁盘上的具体位置信息如数据文件号、块号、行号等。它不是表设计的一部分而是Oracle内部使用的。SELECT ROWID, employee_id, first_name FROM employees WHERE employee_id 100;ROWID的主要用途是极速访问通过ROWID访问某一行是Oracle中最快的方式因为它是直接寻址。判断行是否迁移如果一行数据被更新后无法放入原数据块它可能会被迁移到新块ROWID中的部分信息会变化但逻辑ROWID不变。某些特殊操作如重复数据删除时可以用ROWID来精确定位。 需要注意的是ROWID在表经过MOVE或SHRINK等重组操作后可能会改变因此不应将其作为业务逻辑中的永久标识符存储。4. PL/SQL编程入门从脚本到程序4.1 PL/SQL程序结构匿名块与命名块PL/SQL将SQL的数据操纵能力与过程化语言的流程控制能力结合起来。最基本的单元是“块”分为匿名块和命名块存储过程、函数、触发器等。一个完整的PL/SQL块结构如下DECLARE -- 声明部分定义变量、常量、游标、异常等。可选。 v_employee_name employees.first_name%TYPE; -- 使用%TYPE引用表字段类型是好习惯 v_bonus NUMBER(10,2) : 0; -- 声明并初始化 c_tax_rate CONSTANT NUMBER : 0.05; -- 常量 BEGIN -- 执行部分包含SQL语句和PL/SQL逻辑。必须。 SELECT first_name INTO v_employee_name FROM employees WHERE employee_id 100; v_bonus : calculate_bonus(100); -- 假设有一个函数 DBMS_OUTPUT.PUT_LINE(员工: || v_employee_name || , 奖金: || v_bonus); EXCEPTION -- 异常处理部分处理执行中可能出现的错误。可选。 WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE(未找到员工记录); WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(发生错误: || SQLERRM); END; /匿名块就是上面这种没有名字无法存储在数据库中供其他程序调用通常用于临时性的脚本任务。要执行匿名块需要在SQL工具中打开DBMS_OUTPUT在PL/SQL Developer中按F8在SQL Developer中需要点击启用输出面板。4.2 存储过程与函数的创建与应用命名块中最常用的就是存储过程和函数它们可以被编译、存储在数据库中反复调用。存储过程Procedure用于执行一系列操作不必须返回值。CREATE OR REPLACE PROCEDURE update_employee_salary( p_emp_id IN employees.employee_id%TYPE, p_raise_percent IN NUMBER ) AS v_old_salary employees.salary%TYPE; v_new_salary employees.salary%TYPE; BEGIN -- 查询旧工资 SELECT salary INTO v_old_salary FROM employees WHERE employee_id p_emp_id FOR UPDATE; -- 加锁防止并发更新 -- 计算新工资 v_new_salary : v_old_salary * (1 p_raise_percent / 100); -- 更新 UPDATE employees SET salary v_new_salary WHERE employee_id p_emp_id; COMMIT; -- 在过程中提交需谨慎通常由调用者控制事务 DBMS_OUTPUT.PUT_LINE(员工 || p_emp_id || 薪资从 || v_old_salary || 更新为 || v_new_salary); EXCEPTION WHEN NO_DATA_FOUND THEN RAISE_APPLICATION_ERROR(-20001, 员工ID不存在: || p_emp_id); END update_employee_salary; /调用存储过程EXEC update_employee_salary(100, 10);或BEGIN update_employee_salary(100, 10); END; /函数Function必须返回一个值通常用于计算。CREATE OR REPLACE FUNCTION get_department_avg_salary( p_dept_id IN departments.department_id%TYPE ) RETURN NUMBER AS v_avg_salary NUMBER; BEGIN SELECT AVG(salary) INTO v_avg_salary FROM employees WHERE department_id p_dept_id; RETURN NVL(v_avg_salary, 0); -- 处理部门无员工的情况 END get_department_avg_salary; /调用函数可以在SQL语句中直接使用SELECT get_department_avg_salary(50) FROM DUAL;实操心得存储过程和函数是封装业务逻辑、提高代码复用性和安全性的利器。但要注意过多的业务逻辑写在数据库层可能会使应用层和数据库层耦合过紧不利于扩展。通常将核心的数据处理、强一致性要求的复杂事务放在存储过程而将业务规则判断放在应用层。4.3 游标与循环处理逐行操作的利器当需要处理查询返回的多行数据时就需要用到游标。游标是一个指向结果集的指针允许你逐行处理数据。显式游标使用四步曲声明、打开、获取、关闭。DECLARE CURSOR cur_high_salary IS SELECT employee_id, first_name, salary FROM employees WHERE salary 10000 ORDER BY salary DESC; v_emp_id employees.employee_id%TYPE; v_name employees.first_name%TYPE; v_sal employees.salary%TYPE; BEGIN OPEN cur_high_salary; -- 打开游标 LOOP FETCH cur_high_salary INTO v_emp_id, v_name, v_sal; -- 获取一行 EXIT WHEN cur_high_salary%NOTFOUND; -- 如果没有更多行退出循环 -- 处理数据例如打印或更新 DBMS_OUTPUT.PUT_LINE(v_emp_id || : || v_name || - || v_sal); -- 可以在这里调用其他过程或进行复杂计算 END LOOP; CLOSE cur_high_salary; -- 关闭游标 END; /更简洁的FOR循环游标Oracle提供了更简洁的语法自动处理游标的打开、获取和关闭。BEGIN FOR emp_rec IN ( SELECT employee_id, first_name, salary FROM employees WHERE department_id 50 ) LOOP DBMS_OUTPUT.PUT_LINE(emp_rec.employee_id || : || emp_rec.first_name); -- 可以直接使用 emp_rec.字段名 访问数据 END LOOP; END; /FOR循环游标代码更简洁不易出错比如忘记关闭游标是处理多行数据的首选方式。5. 错误调试与性能优化基础5.1 常见“ORA-”错误解析与处理PL/SQL执行出错时Oracle会抛出以“ORA-”开头的错误代码。学会解读这些错误是调试的基本功。ORA-06502: PL/SQL: numeric or value error这是最常见的错误之一通常由类型转换或数据长度问题引起。DECLARE v_small_number NUMBER(3); BEGIN v_small_number : 1000; -- 这里会引发 ORA-06502因为1000超出了NUMBER(3)的范围 END;排查思路检查变量声明的精度、标度是否足够容纳赋值的数据。检查字符串到数字、日期等类型的隐式转换是否可能失败例如将‘ABC’赋值给数字变量。使用TO_NUMBER,TO_DATE等函数进行显式转换并做好异常处理。错误信息“ORA-06512: at line X”这指出了错误发生的具体行号在你匿名块或程序单元中的行号是定位问题的关键。结合前面的错误代码一起分析。通用异常处理技巧在EXCEPTION部分尽可能捕获具体的异常如NO_DATA_FOUND,TOO_MANY_ROWS,DUP_VAL_ON_INDEX而不是一味使用WHEN OTHERS。使用WHEN OTHERS时务必使用SQLCODE和SQLERRM记录详细的错误信息。EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(错误代码: || SQLCODE); DBMS_OUTPUT.PUT_LINE(错误信息: || SQLERRM); -- 在实际应用中应将错误记录到日志表 INSERT INTO error_log (error_code, error_msg, backtrace, created_time) VALUES (SQLCODE, SQLERRM, DBMS_UTILITY.FORMAT_ERROR_BACKTRACE, SYSDATE); RAISE; -- 选择性地重新抛出异常5.2 SQL注入防御与性能初步考量SQL注入防御搜索词中出现了“cause: java.sql.solexception: sql injection violation, dbtype oracle, druid-”这指向了应用层如Java使用Druid连接池检测到的SQL注入违规。根本原因是在拼接SQL字符串时未对用户输入进行过滤或使用了错误的参数传递方式。危险的做法拼接字符串-- 在PL/SQL中如果动态SQL这样写同样危险 v_sql : SELECT * FROM users WHERE username || p_username || AND password || p_password || ; EXECUTE IMMEDIATE v_sql;如果p_username输入是admin OR 11那么整个SQL语义就被篡改了。正确的做法使用绑定变量在PL/SQL动态SQL中必须使用绑定变量。CREATE OR REPLACE PROCEDURE secure_login( p_username IN VARCHAR2, p_password IN VARCHAR2 ) AS v_count NUMBER; BEGIN -- 使用绑定变量 :1, :2 EXECUTE IMMEDIATE SELECT COUNT(*) FROM users WHERE username :1 AND password :2 INTO v_count USING p_username, p_password; -- 参数按顺序绑定 IF v_count 1 THEN DBMS_OUTPUT.PUT_LINE(登录成功); ELSE DBMS_OUTPUT.PUT_LINE(登录失败); END IF; END;在应用层如Java JDBC必须使用PreparedStatement而不是Statement。Druid等连接池的SQL防火墙功能正是通过检查是否使用绑定变量来防御注入的。性能初步考量避免在循环中执行SQL这是新手最容易犯的性能错误。应尽量使用批量操作或集合操作。-- 差在循环中逐条更新 FOR emp IN (SELECT employee_id FROM employees WHERE ...) LOOP UPDATE salaries SET ... WHERE employee_id emp.employee_id; -- 每条记录一次提交如果循环外没提交则是一条记录一次往返 END LOOP; -- 好使用单条SQL批量更新 UPDATE salaries s SET s.amount (SELECT ...) WHERE EXISTS (SELECT 1 FROM employees e WHERE e.employee_id s.employee_id AND ...);合理使用索引确保WHERE子句和JOIN条件中的字段有索引。但索引不是越多越好维护索引也有开销。使用EXPLAIN PLAN在SQL Developer或PL/SQL Developer中对复杂查询执行EXPLAIN PLAN查看Oracle的执行计划了解SQL是如何被执行的是否存在全表扫描等低效操作。6. 高级主题初探与日常维护操作6.1 数据导入导出实用技巧数据的迁移和备份是日常运维的常见任务。Oracle提供了多种工具如数据泵EXPDP/IMPDP、传统导出导入EXP/IMP以及SQL Developer的图形化工具。使用数据泵推荐适用于大型数据、并行操作数据泵是服务器端的工具效率高功能强。# 命令行导出需要在服务器上执行或通过SSH连接 expdp username/passwordservice_name DIRECTORYDATA_PUMP_DIR DUMPFILEmy_export.dmp SCHEMASmy_schema LOGFILEexport.log # 命令行导入 impdp username/passwordservice_name DIRECTORYDATA_PUMP_DIR DUMPFILEmy_export.dmp REMAP_SCHEMAmy_schema:new_schema LOGFILEimport.logDIRECTORY是一个Oracle目录对象指向服务器文件系统上的一个路径需要DBA提前创建并授权。使用SQL Developer图形化导出适合中小型数据、快速操作在连接上右键选择“工具” - “数据库导出”。选择要导出的对象类型表、数据、DDL等。选择输出为“SQL插入语句”或“单独的文件”。可以方便地生成“idea导出数据库脚本”或其它IDE能识别的脚本。日常小数据量转移-- 使用CREATE TABLE ... AS SELECT ... (CTAS)快速复制表结构和数据 CREATE TABLE employees_backup AS SELECT * FROM employees WHERE department_id 50; -- 使用INSERT INTO ... SELECT ... 插入数据 INSERT INTO target_table (col1, col2) SELECT source_col1, source_col2 FROM source_table WHERE condition;6.2 理解数据库对象与系统视图除了表Oracle数据库还包含视图、序列、同义词、触发器等众多对象。视图View虚拟表基于一个或多个表的查询结果。用于简化复杂查询、隐藏数据细节、提供安全访问层。CREATE OR REPLACE VIEW v_emp_dept AS SELECT e.employee_id, e.first_name, d.department_name FROM employees e JOIN departments d ON e.department_id d.department_id;序列Sequence用于生成唯一的数字序列常用于主键自增虽然Oracle没有自动递增列但常用序列触发器模拟。CREATE SEQUENCE seq_employee_id START WITH 1000 INCREMENT BY 1 NOCACHE NOCYCLE; -- 使用SELECT seq_employee_id.NEXTVAL FROM DUAL;同义词Synonym为数据库对象表、视图、序列等创建别名简化访问隐藏对象位置。CREATE PUBLIC SYNONYM emp FOR hr.employees; -- 创建公有同义词 -- 之后其他用户可以直接 SELECT * FROM emp; 而无需加模式名前缀hr.系统视图查询Oracle提供了大量以USER_、ALL_、DBA_为前缀的数据字典视图用于查询元数据。-- 查看当前用户下的所有表 SELECT table_name FROM user_tables; -- 查看表的列信息 SELECT column_name, data_type, nullable FROM user_tab_columns WHERE table_name EMPLOYEES; -- 查看存储过程、函数等源代码 SELECT text FROM user_source WHERE name UPDATE_EMPLOYEE_SALARY AND type PROCEDURE ORDER BY line;掌握查询这些视图是进行数据库管理、性能分析和问题排查的基础。6.3 锁与并发问题浅析当多个会话用户同时访问和修改同一数据时就可能发生并发问题。Oracle通过锁机制来保证数据的一致性。常见的锁类型行级锁TX锁当执行UPDATE、DELETE或SELECT ... FOR UPDATE时Oracle会自动在被修改的行上加行级独占锁。其他会话可以查询这些行但不能修改。表级锁TM锁在执行DDL语句如ALTER TABLE,DROP TABLE或某些特定的DML操作时会在表上加锁。排查死锁“数据库死锁”是指两个或更多会话互相等待对方持有的锁导致所有会话都无法继续执行。Oracle会自动检测死锁并回滚其中一个会话的事务抛出“ORA-00060: deadlock detected”错误。 当发生死锁时可以查询v$lock和v$session视图来定位问题根源。SELECT s.sid, s.serial#, s.username, s.program, l.type, l.id1, l.id2, l.lmode, l.request, l.block FROM v$lock l JOIN v$session s ON l.sid s.sid WHERE l.type IN (TM, TX) ORDER BY l.sid, l.type;BLOCK列为1表示该会话阻塞了其他会话。找到阻塞链分析相关SQL优化业务逻辑例如确保多个事务以相同的顺序访问资源是解决死锁的根本方法。开发建议事务要尽可能短尽快提交或回滚减少锁的持有时间。访问多个资源时尽量约定一个固定的顺序例如总是先更新A表再更新B表。谨慎使用SELECT ... FOR UPDATE除非确实需要锁定行以备后续更新。可以考虑使用乐观锁通过版本号字段来替代悲观锁。