MySQL 事务隔离级别及实际业务问题详解

一、事务隔离级别基础

1.1 四种隔离级别概述

复制代码
-- MySQL事务隔离级别(从低到高)
-- 1. READ UNCOMMITTED(读未提交)
-- 2. READ COMMITTED(读已提交)
-- 3. REPEATABLE READ(可重复读)-- MySQL默认级别
-- 4. SERIALIZABLE(串行化)

1.2 并发问题类型

复制代码
/**
 * 并发问题分类:
 * 1. 脏读(Dirty Read):读取到其他事务未提交的数据
 * 2. 不可重复读(Non-Repeatable Read):同一事务内两次读取结果不一致
 * 3. 幻读(Phantom Read):同一事务内两次查询结果集不一致
 * 4. 丢失更新(Lost Update):两个事务更新同一数据,后提交的覆盖了先提交的
 */

二、各隔离级别详解

2.1 READ UNCOMMITTED(读未提交)

sql 复制代码
-- 设置隔离级别为读未提交
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

-- 示例:脏读问题演示
-- 事务A
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE user_id = 1;
-- 此时 balance 已修改但未提交

-- 事务B(在另一个连接)
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
SELECT balance FROM account WHERE user_id = 1; 
-- 会读取到事务A未提交的修改(脏读)

-- 如果事务A回滚,事务B读取的数据就是错误的

实际业务场景:

  • 几乎不使用,除非对数据一致性要求极低

  • 可能的用途:实时监控系统,允许数据短暂不一致

2.2 READ COMMITTED(读已提交)

sql 复制代码
-- 设置隔离级别为读已提交
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 示例:不可重复读问题演示
-- 事务A
START TRANSACTION;
SELECT balance FROM account WHERE user_id = 1;
-- 第一次读取:balance = 1000

-- 事务B
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE user_id = 1;
COMMIT; -- 提交修改

-- 事务A再次读取
SELECT balance FROM account WHERE user_id = 1;
-- 第二次读取:balance = 900(不可重复读)
COMMIT;

MVCC(多版本并发控制)实现原理:

sql 复制代码
-- MySQL在READ COMMITTED下的实现
-- 每行数据有隐藏字段:
-- DB_TRX_ID:最后修改该记录的事务ID
-- DB_ROLL_PTR:回滚指针,指向undo log中的旧版本
-- DB_ROW_ID:行ID(隐藏主键)

-- 事务可见性规则:
-- 1. 版本号小于当前事务ID的记录
-- 2. 删除版本号未定义或大于当前事务ID
-- 3. 在READ COMMITTED下,每次查询都重新生成ReadView

实际业务场景:

  • Oracle、PostgreSQL的默认级别

  • 适合大多数OLTP系统

  • 报表系统(需要实时最新数据)

  • 对数据实时性要求高的场景

2.3 REPEATABLE READ(可重复读)- MySQL默认

sql 复制代码
-- MySQL默认隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- 示例:解决不可重复读
-- 事务A
START TRANSACTION;
SELECT balance FROM account WHERE user_id = 1;
-- 第一次读取:balance = 1000

-- 事务B
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE user_id = 1;
COMMIT;

-- 事务A再次读取
SELECT balance FROM account WHERE user_id = 1;
-- 第二次读取:balance = 1000(可重复读,读取的是快照)
COMMIT;

幻读问题演示:

sql 复制代码
-- 事务A:统计账户数量
START TRANSACTION;
SELECT COUNT(*) FROM account WHERE balance > 0;
-- 返回:5

-- 事务B:插入新账户
START TRANSACTION;
INSERT INTO account(user_id, balance) VALUES (6, 500);
COMMIT;

-- 事务A:再次统计
SELECT COUNT(*) FROM account WHERE balance > 0;
-- 在REPEATABLE READ下,返回仍然是:5(没有幻读)
-- 但如果执行UPDATE/INSERT,可能会看到"幻影行"

间隙锁(Gap Lock)解决幻读:

sql 复制代码
-- 事务A
START TRANSACTION;
-- 使用SELECT ... FOR UPDATE添加间隙锁
SELECT * FROM account WHERE id > 100 FOR UPDATE;
-- 这会锁定id>100的所有记录和间隙

-- 事务B试图插入
INSERT INTO account(id, user_id) VALUES (101, 6);
-- 会被阻塞,直到事务A提交

-- 查看当前锁信息
SHOW ENGINE INNODB STATUS;

实际业务场景:

  • 财务系统(需要事务内数据一致性)

  • 对账系统(统计期间数据不能变化)

  • 需要稳定数据视图的应用

2.4 SERIALIZABLE(串行化)

sql 复制代码
-- 串行化隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- 所有SELECT语句都会隐式添加LOCK IN SHARE MODE
-- 事务A
START TRANSACTION;
SELECT balance FROM account WHERE user_id = 1;
-- 自动加共享锁

-- 事务B
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE user_id = 1;
-- 会被阻塞,直到事务A提交

实际业务场景:

  • 银行转账(需要绝对数据一致性)

  • 库存扣减(超高并发时需要串行化)

  • 敏感数据操作(如密码重置)

三、MVCC(多版本并发控制)深度解析

3.1 MVCC实现原理

sql 复制代码
-- InnoDB MVCC数据结构示例
CREATE TABLE account (
    id INT PRIMARY KEY,
    user_id INT,
    balance DECIMAL(10,2),
    -- 隐藏字段
    -- DB_TRX_ID: 6字节,最后修改事务ID
    -- DB_ROLL_PTR: 7字节,回滚指针
    -- DB_ROW_ID: 6字节,隐藏主键
    -- DELETE BIT: 删除标记
);

-- ReadView创建时机:
-- READ COMMITTED: 每次SELECT都创建新的ReadView
-- REPEATABLE READ: 第一次SELECT时创建ReadView,整个事务复用

-- 可见性判断算法:
-- 1. 如果DB_TRX_ID < up_limit_id,可见(事务开始前已提交)
-- 2. 如果DB_TRX_ID >= low_limit_id,不可见(事务开始后开始的)
-- 3. 如果DB_TRX_ID在活跃事务列表中,不可见(未提交)
-- 4. 否则可见

3.2 Undo Log链示例

sql 复制代码
-- 假设原始数据
-- id=1, balance=1000, DB_TRX_ID=10

-- 事务20修改
UPDATE account SET balance = 900 WHERE id = 1;
-- 新版本:balance=900, DB_TRX_ID=20, 回滚指针指向旧版本

-- 事务30修改
UPDATE account SET balance = 800 WHERE id = 1;
-- 新版本:balance=800, DB_TRX_ID=30, 回滚指针指向事务20的版本

-- 版本链:
-- 当前版本(事务30) ← 事务20版本 ← 事务10版本

四、实际业务问题与解决方案

4.1 电商库存超卖问题

sql 复制代码
-- 问题场景:高并发下库存扣减
-- 错误做法(存在超卖风险)
START TRANSACTION;
SELECT stock FROM product WHERE id = 1;
-- 假设stock = 10
IF stock > 0 THEN
    UPDATE product SET stock = stock - 1 WHERE id = 1;
END IF;
COMMIT;

-- 解决方案1:使用SELECT ... FOR UPDATE(悲观锁)
START TRANSACTION;
-- 加行锁,阻止其他事务读取
SELECT stock FROM product WHERE id = 1 FOR UPDATE;
-- 此时其他事务的SELECT ... FOR UPDATE会被阻塞
IF stock > 0 THEN
    UPDATE product SET stock = stock - 1 WHERE id = 1;
END IF;
COMMIT;

-- 解决方案2:使用乐观锁(版本控制)
ALTER TABLE product ADD version INT DEFAULT 0;

START TRANSACTION;
SELECT stock, version FROM product WHERE id = 1;
-- 假设stock=10, version=1

IF stock > 0 THEN
    UPDATE product SET 
        stock = stock - 1,
        version = version + 1 
    WHERE id = 1 AND version = 1;
    -- 如果影响行数为0,说明版本已变,需要重试
END IF;
COMMIT;

-- 解决方案3:直接UPDATE判断
START TRANSACTION;
UPDATE product SET stock = stock - 1 
WHERE id = 1 AND stock > 0;
-- 返回影响行数,如果为0表示库存不足
COMMIT;

4.2 银行转账并发问题

sql 复制代码
-- 场景:A向B转账,需要保证原子性和一致性
-- 问题:并发转账可能导致余额错误

-- 解决方案:使用SERIALIZABLE隔离级别或精心设计的事务
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

START TRANSACTION;

-- 关键:按固定顺序加锁,避免死锁
-- 总是先锁id小的账户
SELECT * FROM account 
WHERE id IN (1, 2) 
ORDER BY id 
FOR UPDATE;

-- 检查A账户余额
SELECT balance FROM account WHERE id = 1;

-- 执行转账
UPDATE account SET balance = balance - 100 WHERE id = 1;
UPDATE account SET balance = balance + 100 WHERE id = 2;

-- 记录流水
INSERT INTO transfer_log(from_id, to_id, amount) VALUES (1, 2, 100);

COMMIT;

4.3 报表统计不一致问题

sql 复制代码
-- 场景:生成财务报表时,数据被其他事务修改
-- 要求:统计期间数据必须一致

-- 解决方案1:使用REPEATABLE READ + 开始时间点
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

START TRANSACTION;

-- 记录开始时间
SET @report_time = NOW();

-- 统计逻辑(所有查询看到的是同一时间点的快照)
SELECT SUM(balance) FROM account;
SELECT COUNT(*) FROM transfer_log WHERE create_time < @report_time;

COMMIT;

-- 解决方案2:使用备份或从库查询
-- 在从库上使用REPEATABLE READ,不影响主库性能
START TRANSACTION;
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- ... 统计查询
COMMIT;

4.4 消息队列消费幂等性问题

sql 复制代码
-- 场景:消息重复消费,需要保证幂等性
-- 问题:重复处理同一消息

CREATE TABLE message_consumed (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    message_id VARCHAR(64) UNIQUE,  -- 消息唯一ID
    status TINYINT DEFAULT 0,
    consume_time DATETIME
);

-- 消费消息时的幂等处理
START TRANSACTION;

-- 方案1:先插入,利用唯一索引保证幂等
INSERT IGNORE INTO message_consumed(message_id, consume_time) 
VALUES ('msg_123', NOW());

-- 如果插入成功(影响行数>0),则处理消息
IF ROW_COUNT() > 0 THEN
    -- 执行业务逻辑
    CALL process_business_logic('msg_123');
    
    UPDATE message_consumed 
    SET status = 1 
    WHERE message_id = 'msg_123';
END IF;

COMMIT;

五、死锁分析与解决

5.1 死锁场景模拟

sql 复制代码
-- 事务A
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE id = 1;
-- 持有id=1的锁

-- 事务B(同时执行)
START TRANSACTION;
UPDATE account SET balance = balance - 200 WHERE id = 2;
-- 持有id=2的锁

-- 事务A继续
UPDATE account SET balance = balance + 100 WHERE id = 2;
-- 等待事务B释放id=2的锁

-- 事务B继续
UPDATE account SET balance = balance + 200 WHERE id = 1;
-- 等待事务A释放id=1的锁
-- 死锁发生!

5.2 死锁检测与解决

sql 复制代码
-- 查看死锁日志
SHOW ENGINE INNODB STATUS;

-- 死锁日志示例:
-- LATEST DETECTED DEADLOCK
-- *** (1) TRANSACTION:
-- TRANSACTION 3100, ACTIVE 2 sec starting index read
-- mysql tables in use 1, locked 1
-- LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
-- MySQL thread id 10, OS thread handle 1234, query id 100 updating
-- UPDATE account SET balance = balance + 100 WHERE id = 2

-- 预防死锁策略:
-- 1. 按相同顺序访问资源
-- 2. 减少事务执行时间
-- 3. 使用低隔离级别(READ COMMITTED)
-- 4. 添加合理的索引,减少锁范围

-- 代码层面的解决方案
START TRANSACTION;

-- 总是按id顺序加锁
SELECT * FROM account 
WHERE id IN (1, 2) 
ORDER BY id 
FOR UPDATE;

-- 执行更新操作
UPDATE account SET balance = balance - 100 WHERE id = 1;
UPDATE account SET balance = balance + 100 WHERE id = 2;

COMMIT;

5.3 间隙锁死锁问题

sql 复制代码
-- 间隙锁导致的死锁场景
-- 表结构
CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT,
    amount DECIMAL(10,2),
    KEY idx_user_id (user_id)
);

-- 事务A
START TRANSACTION;
-- 对user_id=100加间隙锁(锁定100-200之间的间隙)
SELECT * FROM orders WHERE user_id = 150 FOR UPDATE;

-- 事务B
START TRANSACTION;
-- 对user_id=200加间隙锁(锁定100-200之间的间隙)
SELECT * FROM orders WHERE user_id = 180 FOR UPDATE;

-- 事务A尝试插入
INSERT INTO orders(id, user_id) VALUES (1, 160);
-- 等待事务B的间隙锁

-- 事务B尝试插入
INSERT INTO orders(id, user_id) VALUES (2, 170);
-- 等待事务A的间隙锁
-- 死锁!

六、性能优化与最佳实践

6.1 隔离级别选择建议

sql 复制代码
-- 选择合适隔离级别的决策流程

/**
 * 决策树:
 * 1. 需要避免脏读?是 → 至少READ COMMITTED
 * 2. 需要避免不可重复读?是 → 至少REPEATABLE READ
 * 3. 需要避免幻读?是 → SERIALIZABLE
 * 4. 并发性能要求高?是 → 考虑降低隔离级别
 * 5. 有明确的锁策略?是 → 可以使用较低隔离级别+手动加锁
 */

-- 各隔离级别适用场景总结:
-- READ UNCOMMITTED: 统计类只读查询,允许脏数据
-- READ COMMITTED: 大多数OLTP系统,需要实时数据
-- REPEATABLE READ: 财务系统,对账系统,需要稳定视图
-- SERIALIZABLE: 银行核心交易,需要绝对一致性

6.2 事务设计最佳实践

sql 复制代码
-- 实践1:保持事务短小
-- 错误示例:长事务
START TRANSACTION;
-- 复杂业务逻辑
-- 网络调用
-- 文件操作
-- 大量计算
COMMIT; -- 事务持续时间太长

-- 正确示例:拆分事务
-- 事务1:数据准备
START TRANSACTION;
INSERT INTO temp_data SELECT ...;
COMMIT;

-- 事务2:业务处理
START TRANSACTION;
UPDATE ...;
COMMIT;

-- 实践2:避免在事务中执行SELECT ... FOR UPDATE时扫描大量数据
-- 错误示例
START TRANSACTION;
SELECT * FROM orders WHERE create_time > '2023-01-01' FOR UPDATE;
-- 可能锁定大量行,导致性能问题

-- 正确示例:分批处理
DECLARE done INT DEFAULT FALSE;
DECLARE batch_size INT DEFAULT 100;
DECLARE offset INT DEFAULT 0;

WHILE NOT done DO
    START TRANSACTION;
    
    SELECT * FROM orders 
    WHERE create_time > '2023-01-01' 
    LIMIT batch_size OFFSET offset 
    FOR UPDATE;
    
    -- 处理逻辑
    
    COMMIT;
    
    SET offset = offset + batch_size;
    -- 检查是否完成
END WHILE;

6.3 监控与调优

sql 复制代码
-- 监控当前事务
SELECT * FROM information_schema.INNODB_TRX;

-- 监控锁信息
SELECT * FROM information_schema.INNODB_LOCKS;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;

-- 监控长事务
SELECT 
    trx_id,
    trx_started,
    TIMESTAMPDIFF(SECOND, trx_started, NOW()) as duration_seconds,
    trx_state,
    trx_query
FROM information_schema.INNODB_TRX
WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 60  -- 超过60秒的事务
ORDER BY trx_started;

-- 设置长事务超时
SET SESSION innodb_lock_wait_timeout = 50;  -- 锁等待超时50秒
SET SESSION innodb_rollback_on_timeout = ON; -- 超时自动回滚

七、实际案例分析

7.1 电商秒杀系统

sql 复制代码
-- 秒杀场景下的库存扣减优化
-- 表设计
CREATE TABLE seckill_stock (
    product_id BIGINT PRIMARY KEY,
    stock INT NOT NULL,
    version INT DEFAULT 0,
    sale_date DATE
);

-- 方案1:使用乐观锁+重试机制
DELIMITER //
CREATE PROCEDURE seckill_purchase(
    IN p_product_id BIGINT,
    IN p_user_id BIGINT,
    OUT p_result INT
)
BEGIN
    DECLARE v_stock INT;
    DECLARE v_version INT;
    DECLARE retry_count INT DEFAULT 0;
    DECLARE max_retry INT DEFAULT 3;
    
    purchase_retry: REPEAT
        START TRANSACTION;
        
        -- 查询当前库存和版本
        SELECT stock, version INTO v_stock, v_version
        FROM seckill_stock 
        WHERE product_id = p_product_id 
        FOR UPDATE;  -- 悲观锁,防止其他事务同时修改
        
        IF v_stock <= 0 THEN
            ROLLBACK;
            SET p_result = 0; -- 库存不足
            LEAVE purchase_retry;
        END IF;
        
        -- 更新库存
        UPDATE seckill_stock 
        SET stock = stock - 1,
            version = version + 1
        WHERE product_id = p_product_id 
          AND version = v_version;
        
        -- 检查是否更新成功
        IF ROW_COUNT() = 1 THEN
            -- 创建订单
            INSERT INTO seckill_order(product_id, user_id, create_time)
            VALUES (p_product_id, p_user_id, NOW());
            
            COMMIT;
            SET p_result = 1; -- 成功
            LEAVE purchase_retry;
        ELSE
            ROLLBACK;
            SET retry_count = retry_count + 1;
            
            -- 等待随机时间后重试
            DO SLEEP(RAND() * 0.1);
        END IF;
        
    UNTIL retry_count >= max_retry END REPEAT;
    
    IF retry_count >= max_retry THEN
        SET p_result = -1; -- 重试失败
    END IF;
END//
DELIMITER ;

7.2 金融对账系统

sql 复制代码
-- 对账系统需要数据一致性
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

START TRANSACTION;

-- 记录对账开始时间
SET @reconcile_time = NOW();

-- 创建对账快照表
CREATE TEMPORARY TABLE reconcile_snapshot AS
SELECT 
    a.account_no,
    a.balance as db_balance,
    b.balance as external_balance
FROM account a
LEFT JOIN external_system b ON a.account_no = b.account_no
WHERE a.update_time <= @reconcile_time;

-- 执行对账逻辑
SELECT 
    account_no,
    db_balance,
    external_balance,
    CASE 
        WHEN ABS(db_balance - external_balance) > 0.01 THEN 'MISMATCH'
        ELSE 'MATCH'
    END as status
FROM reconcile_snapshot;

-- 记录对账结果
INSERT INTO reconcile_log(reconcile_time, total_count, mismatch_count)
SELECT 
    @reconcile_time,
    COUNT(*),
    SUM(CASE WHEN ABS(db_balance - external_balance) > 0.01 THEN 1 ELSE 0 END)
FROM reconcile_snapshot;

COMMIT;

7.3 社交系统点赞功能

sql 复制代码
-- 点赞功能,需要避免重复点赞
CREATE TABLE likes (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    post_id BIGINT NOT NULL,
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_user_post (user_id, post_id)  -- 唯一约束防止重复
);

-- 点赞操作
START TRANSACTION;

-- 尝试插入,利用唯一约束保证幂等性
INSERT IGNORE INTO likes(user_id, post_id) 
VALUES (123, 456);

-- 如果插入成功,更新帖子点赞数
IF ROW_COUNT() > 0 THEN
    UPDATE posts 
    SET like_count = like_count + 1 
    WHERE id = 456;
END IF;

COMMIT;

-- 取消点赞
START TRANSACTION;

DELETE FROM likes 
WHERE user_id = 123 AND post_id = 456;

-- 如果删除成功,更新帖子点赞数
IF ROW_COUNT() > 0 THEN
    UPDATE posts 
    SET like_count = GREATEST(0, like_count - 1) 
    WHERE id = 456;
END IF;

COMMIT;

八、常见问题与陷阱

8.1 自动提交陷阱

sql 复制代码
-- MySQL默认autocommit=1,每条语句都是一个事务
-- 可能导致意想不到的问题

-- 关闭自动提交
SET autocommit = 0;

-- 显式控制事务
START TRANSACTION;
-- 业务逻辑
COMMIT; -- 或 ROLLBACK;

-- 恢复自动提交
SET autocommit = 1;

8.2 隐式提交操作

sql 复制代码
-- 以下语句会隐式提交当前事务:
-- 1. DDL语句(CREATE, ALTER, DROP等)
-- 2. 用户权限管理语句
-- 3. 锁表语句(LOCK TABLES, UNLOCK TABLES)

-- 错误示例
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE id = 1;

-- 这个语句会提交事务!
CREATE INDEX idx_balance ON account(balance);

UPDATE account SET balance = balance + 100 WHERE id = 2;
-- 如果这里出错,第一个UPDATE已经提交,无法回滚!
COMMIT;

8.3 大事务问题

sql 复制代码
-- 大事务可能导致的问题:
-- 1. 锁持有时间长,阻塞其他事务
-- 2. 产生大量undo log,占用磁盘
-- 3. 主从复制延迟
-- 4. 回滚时间长

-- 解决方案:拆分大事务
-- 原始大事务
START TRANSACTION;
-- 处理10万条数据
UPDATE large_table SET status = 1 WHERE condition;
COMMIT; -- 可能执行几分钟

-- 拆分为小批次
SET autocommit = 0;
SET @rows_affected = 1;

WHILE @rows_affected > 0 DO
    START TRANSACTION;
    
    UPDATE large_table 
    SET status = 1 
    WHERE condition 
    AND status = 0 
    LIMIT 1000;
    
    SET @rows_affected = ROW_COUNT();
    
    COMMIT;
    
    -- 短暂暂停,减少对系统影响
    DO SLEEP(0.1);
END WHILE;

SET autocommit = 1;

总结

MySQL事务隔离级别的选择需要权衡一致性、并发性能和数据准确性:

  1. READ UNCOMMITTED:几乎不用,除非特殊场景

  2. READ COMMITTED:适合大多数OLTP系统,需要实时数据

  3. REPEATABLE READ(MySQL默认):需要稳定数据视图的场景

  4. SERIALIZABLE:需要绝对一致性的关键系统

最佳实践建议

  • 优先使用REPEATABLE READ,配合合理的锁策略

  • 事务尽量短小,避免长事务

  • 合理使用索引,减少锁竞争

  • 监控长事务和死锁,及时优化

  • 根据业务特点选择合适隔离级别,不要盲目追求高隔离级别

性能优化要点

  • 热点数据使用乐观锁+重试机制

  • 批量操作使用分批次处理

  • 避免事务中的网络I/O和复杂计算

  • 使用从库进行统计查询,减轻主库压力

相关推荐
y_想不到名字1 天前
MySQL windows版本免安装
数据库·mysql
lalala_lulu1 天前
什么是事务,事务有什么特性?
java·开发语言·数据库
_清欢l1 天前
Dify+test2data实现自然语言查询数据库
数据库·人工智能·openai
哥只是传说中的小白1 天前
Nano Banana Pro高并发接入Grsai Api实战!0.09/张无限批量生成(附接入实战+开源工具)
开发语言·数据库·ai作画·开源·aigc·php·api
南知意-1 天前
Navicat 17 下载安装教程!
数据库·mysql·navicat·数据库连接
l1t1 天前
DeepSeek辅助编写的利用唯一可选数求解数独SQL
数据库·sql·算法·postgresql
Miqiuha1 天前
数据库分片和分区
数据库
技术净胜1 天前
MySQL 8.0 超详细保姆级官方版下载安装完整教程步骤(含环境配置+安装包下载)
数据库·mysql·adb
闲人编程1 天前
商品管理与库存系统
服务器·网络·数据库·python·api·数据模型·codecapsule