MySQL 锁机制与死锁分析深度解析

MySQL 锁机制与死锁分析深度解析

锁是数据库并发控制的核心。本文深入剖析 InnoDB 的锁类型体系、Next-Key Lock 的加锁规则、意向锁与表锁的关系、死锁产生的原因与排查方法,以及生产环境的锁优化实战技巧。

一、InnoDB 锁概述

1.1 为什么需要锁

解决方式
并发问题
并发冲突
事务A: 读取 x=100
事务B: 读取 x=100
事务A: x=100+50
事务B: x=100+100
事务A: 提交 x=150
事务B: 提交 x=200
最终 x=200 (错误!)
锁机制
排他锁 (X)
共享锁 (S)
保证数据一致性

1.2 InnoDB 锁分类全景

锁类型
特殊锁
意向锁 (IX/IS)
自增锁 (AUTO-INC)
临键锁
按算法
Record Lock
Gap Lock
Next-Key Lock
Insert Intention Lock
按模式
共享锁 (S)
排他锁 (X)
按作用域
行级锁
表级锁
InnoDB 锁

1.3 锁兼容性矩阵

兼容结论
X 与任何锁都不兼容
S 与 S 兼容
IX 与 IS 兼容
IX 与 S 互斥
锁兼容性
X
S
IX
IS
X
S
IX
IS
X

X

X
S
IX
IS
















二、行级锁

2.1 Record Lock(记录锁)

记录锁锁住索引记录本身
示例
SELECT * FROM t WHERE id = 5 FOR UPDATE
锁住 id=5 这条记录
其他事务无法修改 id=5
但可以修改其他记录
Record Lock
索引记录
Record Lock
锁住 id=5 的记录

2.2 Gap Lock(间隙锁)

间隙锁锁住索引记录之间的间隙
作用
防止幻读
阻止其他事务插入新记录
锁定间隙内的插入
Gap Lock
索引值: 1, 5, 10, 15
Gap Lock 锁住 (1, 5) 间隙
Gap Lock 锁住 (5, 10) 间隙
Gap Lock 锁住 (10, 15) 间隙
Gap Lock 锁住 (15, +∞) 间隙

2.3 Next-Key Lock

Next-Key Lock = Record Lock + Gap Lock:
示例
索引值: 1, 5, 10
WHERE id >= 5 FOR UPDATE
锁住 [5, 10) 范围
Record Lock on id=5
Gap Lock on (5, 10)
组合结构
Next-Key Lock
Record Lock
Gap Lock
锁住索引记录本身
锁住索引记录之间的间隙

2.4 Next-Key Lock 示例详解

sql 复制代码
-- 假设索引有值: 1, 5, 10, 15
-- 执行语句: SELECT * FROM t WHERE id >= 5 FOR UPDATE;

-- Next-Key Lock 锁住的区间:
-- 1. 对于 id=5: Next-Key Lock on [5, 10)
--    - Record Lock on id=5
--    - Gap Lock on (5, 10)
-- 2. 对于 id=10: Next-Key Lock on [10, 15)
--    - Record Lock on id=10
--    - Gap Lock on (10, 15)
-- 3. 对于 id=15: Next-Key Lock on [15, +∞)
--    - Record Lock on id=15
--    - Gap Lock on (15, +∞)

2.5 唯一索引的特殊性

对比
普通索引: 锁 (pre, 5] + (5, next)
范围更大
更容易死锁
示例
SELECT * FROM t WHERE id = 5 FOR UPDATE
假设 id 是唯一索引
只锁 id=5 这条记录
不锁 (5, next) 间隙
等值查询优化
唯一索引 + 等值查询
只锁住记录本身
不锁间隙


三、表级锁

3.1 表锁

不推荐原因
粒度太粗
并发能力差
容易造成锁等待
InnoDB 推荐使用行级锁
表锁类型
LOCK TABLES ... READ
表级共享锁
其他事务可读不可写
LOCK TABLES ... WRITE
表级排他锁
阻塞其他事务读写

3.2 意向锁

意向锁 用于协调行级锁表级锁
作用
遍历检查每行
检查 IX/IS
检查表锁时
是否有行级锁?
效率低
O(1) 判断
快速判断是否可以加表锁
意向锁类型
IX: 意图排他锁
表示事务将在某行加 X 锁
IS: 意图共享锁
表示事务将在某行加 S 锁

3.3 意向锁获取顺序

示例
事务A: SELECT * FROM t WHERE id=5 FOR UPDATE
获取行级 X 锁
获取表级 IX
其他事务无法获取表级 X
其他事务可以获取表级 S
加锁流程




请求表级 S 锁
检查 IX?
❌ 阻塞
检查 X?
❌ 阻塞
✅ 获取 IS

3.4 意向锁兼容性

理解
IX 与 S 互斥
因为 IX 意味着要加 X 锁
X 与 S 互斥
兼容性矩阵
| IS | IX | S | X
IS | ✅ | ✅ | ✅ | ❌
IX | ✅ | ✅ | ❌ | ❌
S | ✅ | ❌ | ✅ | ❌
X | ❌ | ❌ | ❌ | ❌


四、自增锁

4.1 AUTO-INC 锁

配置参数
innodb_autoinc_lock_mode
0: 传统 (表级锁)
1: 连续 (默认)
2: 交错 (性能最好)
AUTO-INC 锁特性
表级自增锁
插入时自动获取
插入完成后释放
确保自增 ID 连续

4.2 三种自增锁模式

innodb_autoinc_lock_mode = 2
交错锁模式
所有 INSERT 使用互斥量
性能最好
不保证自增ID连续
主从复制可能有问题
innodb_autoinc_lock_mode = 1
连续锁模式
简单 INSERT 使用互斥量
批量 INSERT 使用 AUTO-INC 锁
保证自增ID连续
innodb_autoinc_lock_mode = 0
传统锁模式
使用 AUTO-INC 锁
表级锁,效率低
语句执行完后释放


四·续、锁的内存结构与 MDL 锁

4.3 锁的内存结构

Record Lock 内存结构
lock_rec_t
rec_id: 页号 + 记录号
n_bits: 位图大小
bits[]: 记录锁状态

每个 bit 对应一条记录
InnoDB 锁对象结构
lock_t 结构
lock_type_t type

锁类型 (RECORD/TABLE)
ibool impl_trx

持有锁的事务
dict_table_t *tab_def

锁关联的表
lock_rec_t rec_lock

行锁信息
ulint space, page_no, n_bits

表空间、页号、位图

4.4 锁信息查询详解

sql 复制代码
-- 查看当前所有锁
SELECT 
    ENGINE_LOCK_ID,
    ENGINE_TRANSACTION_ID,
    THREAD_ID,
    EVENT_ID,
    OBJECT_SCHEMA,
    OBJECT_NAME,
    LOCK_TYPE,
    LOCK_MODE,
    LOCK_STATUS,
    LOCK_DATA
FROM performance_schema.data_locks
ORDER BY ENGINE_TRANSACTION_ID;

-- 查看锁等待关系
SELECT 
    REQUESTING_THAPHREAD_ID,
    REQUESTING_ENGINE_LOCK_ID,
    REQUESTING_ENGINE_TRANSACTION_ID,
    BLOCKING_ENGINE_LOCK_ID,
    BLOCKING_ENGINE_TRANSACTION_ID
FROM performance_schema.data_lock_waits;

-- 查看具体锁模式
SELECT 
    OBJECT_NAME,
    INDEX_NAME,
    LOCK_MODE,
    LOCK_STATUS,
    LOCK_DATA
FROM performance_schema.data_locks
WHERE OBJECT_NAME = 'orders';

4.5 MDL 锁(元数据锁)

MDL 锁获取场景
MDL_EXCLUSIVE 获取场景
ALTER TABLE
DROP TABLE
RENAME TABLE
ANALYZE TABLE
MDL 锁类型
MDL 锁
MDL_SHARED: 只读访问
MDL_EXCLUSIVE: 修改表结构
MDL_INTENTION_EXCLUSIVE
MDL_SHARED_READ_ONLY
MDL_SHARED_WRITE_LOW_PRIO

4.6 MDL 锁问题排查

sql 复制代码
-- 查看 MDL 锁等待
SELECT 
    THREAD_ID,
    PROCESSLIST_ID,
    PROCESSLIST_USER,
    PROCESSLIST_HOST,
    PROCESSLIST_DB,
    PROCESSLIST_COMMAND,
    PROCESSLIST_STATE,
    PROCESSLIST_INFO
FROM performance_schema.THREADS
WHERE PROCESSLIST_COMMAND != 'Sleep';

-- 查看 MDL 锁详情
SELECT 
    OBJECT_SCHEMA,
    OBJECT_NAME,
    COLUMN_NAME,
    COLUMN_COUNT,
    STORAGE,
    ENGINE,
    TABLE_ID,
    PARTITION_NAME,
    SUBPARTITION_NAME,
    INDEX_NAME,
    SCOPE
FROM performance_schema.METADATA_LOCKS;

-- 查看 MDL 锁等待
SELECT 
    REQUESTING_THREAD_ID,
    REQUESTING_EVENT_ID,
    REQUESTING_OBJECT_SCHEMA,
    REQUESTING_OBJECT_NAME,
    BLOCKING_THREAD_ID,
    BLOCKING_EVENT_ID,
    BLOCKING_OBJECT_SCHEMA,
    BLOCKING_OBJECT_NAME,
    BLOCKING_METADATA_LOCK_STATUS
FROM performance_schema.METADATA_LOCK_WAITS;

4.7 常见 MDL 锁问题

sql 复制代码
-- 问题1: 大查询阻塞 DDL
-- 会话1: 执行大查询
SELECT * FROM orders INTO OUTFILE '/tmp/orders.csv';

-- 会话2: 尝试修改表结构 (阻塞)
ALTER TABLE orders ADD COLUMN remark VARCHAR(500);

-- 会话3: 尝试插入数据 (阻塞)
INSERT INTO orders VALUES (...);

-- 解决方案: 使用 pt-online-schema-change

-- 问题2: 长事务阻塞 DDL
-- 会话1: 开启事务
BEGIN;
SELECT * FROM orders LIMIT 1;

-- 会话2: 尝试 DDL (获取 MDL 锁失败)
ALTER TABLE orders ADD COLUMN remark VARCHAR(500);

-- 解决方案: 设置 lock_wait_timeout
SET SESSION lock_wait_timeout = 10;

四·续续、锁升级与锁优化

4.8 锁升级机制

锁升级影响
锁升级的影响
并发能力下降
死锁可能性降低
锁等待时间可能增加
锁升级过程
行锁 -> 表锁
统计 UPDATE/DELETE 影响的记录数
如果 > 阈值,升级为表锁
避免锁结构过多
锁升级条件
锁升级触发
innodb_table_locks = 1 (默认)
UPDATE/DELETE 影响多行
超过阈值 (默认 2000 行)

4.9 锁优化最佳实践

监控指标
关键监控指标
lock_wait_timeout
innodb_row_lock_time_avg
lock_row_lock_current_count
InnoDB row locks
具体措施
优化措施

  1. 使用主键或唯一索引查询
  2. 避免范围查询加锁
  3. 业务层控制并发
  4. 合理设计索引
  5. 拆分大事务
    优化原则
    锁优化核心
    减少锁持有时间
    降低锁粒度
    避免锁冲突

4.10 乐观锁实现

java 复制代码
// 基于版本号的乐观锁
@Entity
@Table(name = "accounts")
public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private BigDecimal balance;
    
    @Version
    private Long version;  // 版本字段
}

// Service 实现
@Service
public class AccountService {
    
    @Transactional
    public void transfer(Long fromId, Long toId, BigDecimal amount) {
        // 重试机制
        int retry = 3;
        while (retry > 0) {
            try {
                Account from = accountRepository.findById(fromId)
                    .orElseThrow(() -> new RuntimeException("账户不存在"));
                Account to = accountRepository.findById(toId)
                    .orElseThrow(() -> new RuntimeException("账户不存在"));
                
                from.setBalance(from.getBalance().subtract(amount));
                to.setBalance(to.getBalance().add(amount));
                
                accountRepository.save(from);
                accountRepository.save(to);
                break;  // 成功则退出
            } catch (OptimisticLockException e) {
                retry--;
                if (retry == 0) throw e;
                // 等待后重试
                Thread.sleep(100);
            }
        }
    }
}

五、死锁原理

5.1 死锁的定义

死锁四要素
互斥: 资源只能被一个事务持有
持有并等待: 持有资源同时等待其他资源
不抢占: 事务不能抢别人的资源
循环等待: 形成资源等待环
死锁形成
事务A 持有锁1,等待锁2
事务B 持有锁2,等待锁1
形成循环等待
双方都无法继续
死锁

5.2 死锁产生场景

sql 复制代码
-- 场景1: 不同顺序更新多行
-- 事务A
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;  -- 锁住 id=1
UPDATE accounts SET balance = balance + 100 WHERE id = 2;  -- 等待 id=2

-- 事务B
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 2;  -- 锁住 id=2
UPDATE accounts SET balance = balance + 100 WHERE id = 1;  -- 等待 id=1
-- 死锁!

-- 场景2: 索引范围锁
-- 事务A
SELECT * FROM orders WHERE amount > 100 FOR UPDATE;
-- 锁住 (100, +∞) 范围

-- 事务B
INSERT INTO orders VALUES (200, ...);
-- 被阻塞

-- 事务A 回滚后,事务B 可能与事务C 死锁

5.3 死锁时序图

事务管理器 事务B 事务A 事务管理器 事务B 事务A 锁住 id=1 锁住 id=2 事务A 成功 BEGIN BEGIN UPDATE WHERE id=1 (获取X锁1) UPDATE WHERE id=2 (获取X锁2) UPDATE WHERE id=2 (等待X锁2) UPDATE WHERE id=1 (等待X锁1) 检测到死锁 发送 ROLLBACK 继续执行 COMMIT

5.4 InnoDB 死锁处理

处理策略
回滚代价最小的事务
基于 undo log 数量判断
undo 少的事务先回滚
检测机制
等待图 (Wait-For Graph)
节点: 事务
边: 等待关系
检测到环 -> 死锁


六、死锁排查与解决

6.1 查看死锁信息

sql 复制代码
-- 查看当前锁等待
SHOW ENGINE INNODB STATUS;

-- 输出示例
---TRANSACTION 12345, ACTIVE 5 sec
-- mysql tables in use 1, locked 1
-- LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
-- MySQL thread id 123, OS thread handle 0x7f8a, query id 456
-- UPDATE accounts SET balance = balance - 100 WHERE id = 2
-- Waiting for lock at gap of index `idx_id` of table `test`.`accounts`
-- LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
-- locks gap after rec in index `idx_id` of table `test`.`accounts`

6.2 死锁日志分析

sql 复制代码
-- 查看锁信息
SELECT 
    trx_id,
    trx_state,
    trx_started,
    trx_rows_locked,
    trx_rows_modified,
    trx_wait_for
FROM information_schema.INNODB_TRX;

-- 查看锁等待关系
SELECT 
    requesting_trx_id,
    blocking_trx_id,
    lock_mode,
    lock_type,
    lock_table,
    lock_index,
    lock_space,
    lock_page,
    lock_rec,
    lock_data
FROM information_schema.INNODB_LOCK_WAITS;

-- 查看锁详情
SELECT 
    lock_id,
    lock_trx_id,
    lock_mode,
    lock_type,
    lock_table,
    lock_index,
    lock_space,
    lock_page,
    lock_rec,
    lock_data
FROM information_schema.INNODB_LOCKS;

6.3 避免死锁的策略

预防策略
固定顺序访问
按 ID 顺序更新
避免循环等待
减少锁范围
使用主键查询
避免范围查询
减小事务
及时提交
避免长事务
合理隔离级别
读已提交可减少锁
但需注意幻读

6.4 代码层面优化

java 复制代码
// 反例: 随机顺序更新
@Transactional
public void transferRandom(int fromId, int toId, BigDecimal amount) {
    // 随机顺序,可能死锁
    if (Math.random() > 0.5) {
        accountMapper.decrease(fromId, amount);
        accountMapper.increase(toId, amount);
    } else {
        accountMapper.decrease(toId, amount);
        accountMapper.increase(fromId, amount);
    }
}

// 正例: 固定顺序更新
@Transactional
public void transferOrdered(int fromId, int toId, BigDecimal amount) {
    // 按 ID 大小固定顺序
    int firstId = Math.min(fromId, toId);
    int secondId = Math.max(fromId, toId);
    
    if (firstId == fromId) {
        accountMapper.decrease(fromId, amount);
        accountMapper.increase(toId, amount);
    } else {
        accountMapper.decrease(toId, amount);
        accountMapper.increase(fromId, amount);
    }
}

// 更优: 使用乐观锁
@Version
private Long version;

public boolean transferOptimistic(int fromId, int toId, BigDecimal amount) {
    // 先扣减,利用乐观锁冲突检测
    int affected = accountMapper.decreaseWithVersion(
        fromId, amount, version);
    if (affected == 0) {
        throw new OptimisticLockException("余额已变化");
    }
    accountMapper.increase(toId, amount);
    return true;
}

6.5 SQL 层面优化

sql 复制代码
-- 反例: 范围查询(锁住大量记录)
UPDATE orders 
SET status = 'completed' 
WHERE create_time > '2024-01-01' AND create_time < '2024-12-31';
-- 可能锁住数万条记录,死锁风险高

-- 正例1: 主键精准查询
UPDATE orders 
SET status = 'completed' 
WHERE id = 123;
-- 只锁一条记录

-- 正例2: 分批处理
UPDATE orders 
SET status = 'completed' 
WHERE id IN (SELECT id FROM orders WHERE status = 'pending' LIMIT 1000);
-- 分批小事务

-- 正例3: 使用低区分度字段
UPDATE orders 
SET status = 'completed' 
WHERE status = 'pending' AND DATE(create_time) = '2024-06-01';
-- 按小批量日期拆分

七、锁监控与优化

7.1 锁相关参数

sql 复制代码
-- 查看锁相关配置
SHOW VARIABLES LIKE 'innodb_lock%';

-- innodb_lock_wait_timeout: 锁等待超时时间(默认50秒)
-- innodb_lock_strict_mode: 严格模式,忽略唯一索引空值
-- innodb_print_lock_atomic_bugs: 打印锁相关的原子性bug

7.2 监控慢查询

sql 复制代码
-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;  -- 超过1秒记录
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';

-- 查找长时间锁等待
SELECT 
    trx_mysql_thread_id,
    trx_query,
    trx_state,
    trx_started,
    UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(trx_started) AS duration
FROM information_schema.INNODB_TRX
WHERE trx_started < NOW() - INTERVAL 10 SECOND;

7.3 性能优化建议

隔离级别
选择合适隔离级别
读已提交可减少锁
但需注意幻读处理
事务优化
减小事务范围
避免长事务
及时提交
查询优化
避免全表扫描
使用主键或索引查询
减少锁覆盖的记录数
索引优化
合理创建索引
减少扫描范围
使用覆盖索引


八、面试高频问题

8.1 InnoDB 行级锁是如何实现的?

复制代码
InnoDB 行级锁依赖于索引:

1. 锁住索引而非数据行
   - 如果 UPDATE 使用主键索引
     -> 锁住主键索引
   - 如果 UPDATE 使用普通索引
     -> 锁住普通索引 + 主键索引

2. 加锁过程
   - 定位到第一个满足条件的记录
   - 对记录加 Record Lock
   - 对记录之间的间隙加 Gap Lock
   - 组合为 Next-Key Lock
   - 继续扫描,对每条记录加 Next-Key Lock

3. 释放过程
   - 按加锁顺序反向释放
   - 事务提交时统一释放

8.2 什么是 Next-Key Lock?解决了什么问题?

复制代码
Next-Key Lock = Record Lock + Gap Lock

解决的问题:幻读

示例:
假设表有数据: id=1, 5, 10

事务A:
  SELECT * FROM t WHERE id >= 5;  -- 读到 id=5, 10

事务B:
  INSERT INTO t VALUES (6);  -- 插入 id=6

  -- 如果没有 Gap Lock,事务A 再次查询会看到 id=6(幻读)
  -- 如果有 Gap Lock,INSERT 会被阻塞或回滚

  -- Next-Key Lock 锁住 [5, 10),INSERT INTO (6) 被阻塞

8.3 什么是意向锁?为什么需要意向锁?

复制代码
意向锁是表级锁,表示事务将在某行加锁:

- IS (Intent Share Lock): 意图加共享锁
- IX (Intent Exclusive Lock): 意图加排他锁

为什么需要:
假设要加表级 X 锁
  -> 需要检查是否有行级 X/S 锁
  -> 逐行检查效率低 O(n)
  
使用意向锁:
  -> 只需检查表级 IX/IS
  -> O(1) 判断

8.4 如何避免死锁?

复制代码
1. 固定顺序访问资源
   - 按主键 ID 排序后操作
   - 避免循环等待

2. 减小锁范围
   - 使用主键精准查询
   - 避免范围查询

3. 使用低隔离级别
   - READ COMMITTED 减少 Gap Lock
   - 但需要注意幻读

4. 使用乐观锁
   - 通过版本号控制并发
   - 减少锁冲突

5. 监控和超时处理
   - 设置合理的锁等待超时
   - 及时发现和处理死锁

8.5 死锁和锁等待的区别?

复制代码
┌─────────────────────────────────────────────────────────────┐
│ 锁等待                                                                │
├─────────────────────────────────────────────────────────────┤
│ 事务A 持有锁,等待事务B 释放锁                                       │
│ 事务B 持有锁,等待事务C 释放锁                                       │
│ 事务C 持有锁,等待事务A 释放锁                                       │
│ -> 形成等待链,但还没有死锁                                          │
│ -> 正常情况,设置超时时间即可                                        │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│ 死锁                                                                    │
├─────────────────────────────────────────────────────────────┤
│ 事务A 持有锁1,等待锁2                                                │
│ 事务B 持有锁2,等待锁1                                                │
│ -> 形成循环等待                                                       │
│ -> InnoDB 检测到死锁后,选择代价小的事务回滚                          │
└─────────────────────────────────────────────────────────────┘

区别:
- 锁等待: A等B,可能最终A或B完成
- 死锁: A等B,B等A,无法自行解除,需要回滚

九、总结

9.1 锁类型总结

表级锁
LOCK TABLES
意向锁 (IX/IS)
AUTO-INC 锁
行级锁
Record Lock
Gap Lock
Next-Key Lock

9.2 死锁处理流程

复制代码
死锁处理流程:

1. 检测死锁
   - InnoDB 使用 Wait-For Graph
   - 检测到环则死锁

2. 选择回滚事务
   - 基于 undo log 数量
   - 选择代价小的事务回滚

3. 回滚并通知
   - 发送错误给客户端
   - 错误码: 1213 Deadlock found

4. 业务处理
   - 业务层捕获异常
   - 重试或人工处理

9.3 最佳实践

复制代码
锁优化最佳实践:

1. 索引设计
   ✅ 使用主键或索引查询
   ✅ 创建合适的索引减少扫描范围
   
2. SQL 编写
   ✅ 使用精准查询
   ❌ 避免范围查询 FOR UPDATE
   
3. 事务设计
   ✅ 事务尽量短小
   ✅ 避免长事务
   ✅ 固定顺序访问资源
   
4. 隔离级别
   ✅ 根据业务选择合适隔离级别
   ✅ 读多写少可考虑 RC
   
5. 监控
   ✅ 监控锁等待时间
   ✅ 分析慢查询日志
   ✅ 定期检查死锁日志
相关推荐
gQ85v10Db1 小时前
Redis分布式锁进阶第二十二篇
数据库·redis·分布式
千百元1 小时前
mysql5.7 定时删除表数据
mysql
曹牧2 小时前
Oracle:将包含属性(Attributes)的 XML 数据解析为表格数据
xml·数据库·oracle
@小匠2 小时前
Redis RDB持久化之 save 自动备份检查机制
数据库·redis·bootstrap
折哥的程序人生 · 物流技术专研2 小时前
从“卡死”到“跑通”:WMS机器学习全流程实战排坑记
数据库·人工智能·机器学习
上海云盾商务经理杨杨2 小时前
Web渗透核心漏洞:SQL注入漏洞测试与修复实战
数据库·sql·安全
2303_821287382 小时前
c++ RAII机制详解 c++如何利用RAII管理资源
jvm·数据库·python
小宇的天下3 小时前
Calibre 3Dstack --每日一个命令day25【no_trace】(3-25)
数据库
m0_624578593 小时前
C#怎么获取U盘的插拔事件_C#如何重写WndProc捕获消息【进阶】
jvm·数据库·python