当前位置: 首页 > news >正文

通用:MySQL-InnoDB事务及ACID特性

MySQL-InnoDB事务与ACID特性深度解析:从原理到工作实战

MySQL数据库开发中,事务是保障“数据一致性”的核心机制——无论是电商的“下单扣库存”,还是金融的“转账汇款”,都依赖事务确保“要么全部成功,要么全部失败”。而InnoDB作为MySQL唯一支持事务的存储引擎,其对ACID特性的实现逻辑,直接决定了业务数据的安全性与并发性能。

很多开发者对事务的理解停留在“BEGIN/COMMIT/ROLLBACK”的语法层面,却不清楚InnoDB如何通过redo log、undo log、锁机制保障ACID,也容易在高并发场景下因事务配置不当导致“脏读”“数据丢失”等问题。本文将从ACID特性的底层实现出发,结合工作中的典型场景,系统讲解事务的使用、优化与配置,建立完整的事务认知体系。

一、事务基础:什么是事务?为什么需要事务?

在讲解ACID前,需先明确事务的核心定义与业务价值——理解“事务解决了什么问题”,才能更深入地掌握其实现原理。

1.1 事务的定义

事务(Transaction)是数据库中“一组不可分割的SQL操作序列”,这组操作要么全部执行成功(COMMIT),要么全部执行失败(ROLLBACK),不会出现“部分成功、部分失败”的中间状态。

典型业务场景示例(电商下单)
一个完整的下单流程包含3个SQL操作:

  1. 插入订单记录(INSERT INTO orders …);
  2. 扣减商品库存(UPDATE products SET stock = stock-1 WHERE product_id=…);
  3. 增加用户积分(UPDATE users SET points = points+10 WHERE user_id=…);
    这3个操作必须封装为一个事务——若第2步扣库存成功,但第3步加积分失败,需回滚所有操作,避免“库存扣了但积分没加”的业务异常。

1.2 事务的核心价值

事务的存在主要解决两类问题:

  1. 数据一致性问题:避免因SQL执行中断(如数据库崩溃、网络异常)导致的数据逻辑错误;
  2. 并发冲突问题:在多用户同时操作同一数据时(如秒杀抢库存),避免“超卖”“脏读”等并发问题。

二、ACID特性深度解析:InnoDB如何保障?

ACID是事务的四大核心特性,也是衡量数据库事务能力的标准。InnoDB通过不同的底层机制分别保障这四大特性,这是理解事务的关键。

2.1 原子性(Atomicity):要么全成,要么全败

定义:事务中的所有SQL操作是一个不可分割的整体,要么全部执行成功并提交,要么全部执行失败并回滚,不会留下中间状态。

InnoDB的实现机制:Undo Log(回滚日志)

Undo Log是InnoDB用于“回滚数据”的核心日志,其工作原理如下:

  1. 记录反向操作:在执行每个SQL操作前,InnoDB会先将“数据修改前的状态”记录到Undo Log中(如执行UPDATE前,记录“旧值”;执行INSERT前,记录“待删除的行”);
    • 示例:执行UPDATE products SET stock=99 WHERE product_id=1(原stock=100),Undo Log会记录“product_id=1的stock应恢复为100”;
  2. 事务回滚时使用:若事务执行过程中出现错误(如SQL语法错误、业务校验失败)或主动执行ROLLBACK,InnoDB会通过Undo Log的“反向操作”将数据恢复到事务开始前的状态;
  3. 事务提交后释放:事务COMMIT后,Undo Log不会立即删除,而是标记为“可回收”,由InnoDB后台线程(purge线程)在合适时机清理(用于MVCC的读快照)。
工作中注意事项:
  • 事务内的SQL不宜过多:若事务包含上千条SQL,Undo Log会占用大量磁盘空间,且回滚时耗时更长;
  • 避免长事务:长事务会导致Undo Log无法及时清理,可能引发磁盘空间溢出(配置innodb_undo_log_truncate可自动截断过大的Undo Log)。

2.2 一致性(Consistency):事务执行前后数据逻辑一致

定义:事务执行前后,数据库中的数据必须满足“业务逻辑规则”,即从一个一致状态转换到另一个一致状态。

InnoDB的保障机制:多机制协同

一致性是ACID中最核心的特性,也是其他三个特性(A、I、D)共同作用的结果:

  1. 原子性保障:通过Undo Log回滚错误操作,避免中间状态;
  2. 隔离性保障:通过锁机制和MVCC避免并发操作干扰数据;
  3. 持久性保障:通过Redo Log确保提交后的数据不丢失;
  4. 业务层保障:InnoDB仅保障“数据库层面的一致性”,业务层面的一致性需通过SQL逻辑实现(如扣库存前校验stock>0)。
典型业务一致性案例(避免超卖):
-- 错误写法:未校验库存,可能导致超卖(多个事务同时执行时,stock可能变为负数)
BEGIN;
UPDATE products SET stock = stock-1 WHERE product_id=1; -- 风险:stock=0时仍会执行
COMMIT;-- 正确写法:在UPDATE中加入库存校验,保障业务一致性
BEGIN;
-- 仅当stock>0时才扣减,避免超卖
UPDATE products SET stock = stock-1 WHERE product_id=1 AND stock > 0;
-- 检查影响行数,若为0说明库存不足,回滚事务
IF ROW_COUNT() = 0 THENROLLBACK;RETURN '库存不足';
END IF;
COMMIT;

2.3 隔离性(Isolation):并发事务互不干扰

定义:多个事务同时执行时,一个事务的操作不会被另一个事务“看到”中间状态,避免并发操作导致的“脏读”“不可重复读”“幻读”等问题。

1. 并发事务的三大问题

在讲解隔离级别前,需先明确并发事务可能出现的问题,这是隔离级别的设计依据:

问题类型定义示例
脏读(Dirty Read)事务A读取了事务B“未提交”的修改数据,若事务B后续回滚,事务A读取的就是“无效数据”事务B执行“UPDATE users SET balance=1000 WHERE user_id=1”(未提交),事务A读取到balance=1000,随后事务B回滚,事务A读取的1000是脏数据
不可重复读(Non-repeatable Read)事务A在同一事务内多次读取同一数据,期间事务B修改并提交了该数据,导致事务A两次读取结果不一致事务A第一次读取user_id=1的balance=500,事务B修改并提交balance=1000,事务A再次读取时balance=1000,结果不一致
幻读(Phantom Read)事务A在同一事务内多次执行“范围查询”,期间事务B插入/删除了符合范围条件的数据,导致事务A两次查询的“行数不一致”事务A查询“product_id<10的商品”(共5条),事务B插入1条product_id=8的商品并提交,事务A再次查询时变为6条,出现“幻影行”
2. MySQL的四种隔离级别

MySQL支持四种隔离级别(从低到高),不同级别对并发问题的解决能力不同,性能也不同:

隔离级别脏读不可重复读幻读实现机制性能
读未提交(Read Uncommitted, RU)允许允许允许无锁,直接读取最新数据最高
读已提交(Read Committed, RC)禁止允许允许MVCC(多版本并发控制)较高
可重复读(Repeatable Read, RR)禁止禁止禁止(InnoDB特殊优化)MVCC+Next-Key Lock中等
串行化(Serializable)禁止禁止禁止表级锁,事务串行执行最低

注意:InnoDB的默认隔离级别是可重复读(RR),且通过“Next-Key Lock”机制额外解决了幻读问题(这是InnoDB与其他数据库的差异点)。

3. InnoDB的核心实现机制

不同隔离级别依赖不同的机制实现,核心包括MVCC和锁机制:

  • MVCC(多版本并发控制):用于RC和RR隔离级别,通过“数据多版本快照”实现“读不加锁、写不阻塞读”;
    • 原理:每行数据包含DB_TRX_ID(最后修改事务ID)和DB_ROLL_PTR(指向Undo Log的指针),读取时通过“事务ID对比”选择合适的历史版本(快照),避免读取未提交的数据;

    MVCC在前面的文章有介绍过: 通用:MySQL-深入理解MySQL中的MVCC:原理、实现与实战价值

  • Next-Key Lock:用于RR隔离级别,是“行锁+间隙锁”的组合,可锁定“数据行及相邻的间隙”,避免并发插入导致的幻读;

    Next-Key Lock在前面的文章介绍过:通用:MySQL-InnoDB如何解决幻读问题——间隙锁

    • 示例:执行UPDATE products SET stock=99 WHERE product_id BETWEEN 1 AND 10,Next-Key Lock会锁定product_id=1~10的行,以及product_id<1和>10的间隙,防止其他事务插入product_id=5的新行。
4. 工作中隔离级别的选择建议
  • 读已提交(RC):适合“对数据一致性要求不高,但追求高并发”的场景(如商品列表查询、用户行为统计);
    • 优势:并发性能好,避免脏读,且Undo Log清理更快;
    • 配置:SET GLOBAL transaction_isolation = 'READ-COMMITTED';
  • 可重复读(RR):适合“对数据一致性要求高”的场景(如订单创建、库存扣减);
    • 优势:完全避免脏读、不可重复读和幻读,数据安全性高;
    • 配置:默认级别,无需修改(transaction_isolation = 'REPEATABLE-READ');
  • 串行化(Serializable):仅适合“数据一致性要求极高,但并发量极低”的场景(如金融核心对账);
    • 劣势:会导致大量锁等待,并发性能差,不推荐高并发业务。

2.4 持久性(Durability):事务提交后数据不丢失

定义:事务一旦提交(COMMIT),其修改的数据会永久保存在数据库中,即使后续发生数据库崩溃、服务器断电等故障,数据也不会丢失。

InnoDB的实现机制:Redo Log(重做日志)

Redo Log是InnoDB保障数据持久性的核心日志,其工作原理可概括为“Write-Ahead Logging(WAL)”机制——先写日志,再写数据

具体执行流程:
  1. 事务执行阶段
    • 执行SQL时,InnoDB先将“数据修改的内容”记录到Redo Log Buffer(内存缓冲区);
    • 同时修改内存中的数据页(InnoDB Buffer Pool),此时数据仅在内存中,未写入磁盘(减少磁盘IO);
  2. 事务提交阶段(COMMIT)
    • InnoDB将Redo Log Buffer中的日志写入磁盘上的Redo Log File(持久化);
    • 待Redo Log写入成功后,事务提交成功(返回COMMIT OK);
    • 后续InnoDB会通过“后台线程”将内存中修改的数据页异步写入磁盘(刷脏页);
  3. 故障恢复阶段
    • 若数据库崩溃时,内存中的脏页未写入磁盘,重启后InnoDB会读取Redo Log,将“已提交但未刷盘”的数据重新应用到磁盘,确保数据不丢失。
Redo Log的关键特性:
  • 循环写入:Redo Log File由多个文件组成(如ib_logfile0、ib_logfile1),采用“循环覆盖”的方式写入,当日志写满时,会覆盖最早的已刷盘日志;
  • 物理日志:Redo Log记录的是“数据页的物理修改”(如“修改表空间123、数据页456的第78字节为0xAB”),而非SQL逻辑,恢复速度更快。

三、事务相关核心配置项(my.cnf/my.ini)

InnoDB的事务行为可通过配置项调整,合理配置能在“数据安全性”与“性能”之间找到平衡,以下是工作中高频使用的核心配置:

配置项推荐值说明与ACID的关联
transaction_isolationREPEATABLE-READ(默认);READ-COMMITTED(高并发场景)设置MySQL的默认事务隔离级别直接影响隔离性(I),决定是否允许脏读、不可重复读
innodb_support_xaON(默认,分布式事务场景);OFF(非分布式场景)是否支持XA事务(分布式事务协议,用于跨数据库事务)保障分布式场景下的原子性(A),避免跨库事务部分提交
innodb_undo_log_truncateON(推荐)是否自动截断过大的Undo Log(避免磁盘空间溢出)优化原子性(A)的实现,防止Undo Log无限增长
innodb_undo_tablespaces2(推荐,MySQL 5.7+)Undo Log的表空间数量(独立于系统表空间,便于管理)提升Undo Log的读写性能,间接保障原子性(A)
innodb_flush_log_at_trx_commit1(核心业务);2(非核心业务);0(测试环境)事务提交时Redo Log的刷盘策略(WAL机制的关键配置)直接影响持久性(D)与性能的平衡:
- 1:提交时立即刷盘,完全保障持久性,性能最低;
- 2:提交时写入OS Cache,操作系统定期刷盘,崩溃可能丢失1秒内数据;
- 0:后台线程每秒刷盘,崩溃可能丢失1秒内数据,性能最高
innodb_log_buffer_size64M-128M(大事务场景可设为256M)Redo Log的内存缓冲区大小减少事务执行过程中Redo Log的磁盘写入次数,提升性能,不影响持久性(D)
innodb_log_file_size2G-4G(单个文件)Redo Log文件的大小(一组文件的总大小建议≤InnoDB Buffer Pool的40%)影响Redo Log的切换频率:过小会频繁切换并触发刷脏页,影响性能;过大则故障恢复时间变长,不影响持久性(D)
innodb_lock_wait_timeout5-10(默认50秒,推荐缩短)事务等待行锁的超时时间(超过则报“Lock wait timeout exceeded”错误)避免长事务占用锁导致其他事务无限等待,提升并发性能,间接保障隔离性(I)

四、工作中事务的典型问题与解决方案

掌握ACID特性后,还需解决工作中常见的事务问题——如长事务、锁等待、并发超卖等,这些问题直接影响业务的稳定性与性能。

4.1 问题1:长事务导致锁等待与Undo Log膨胀

现象:业务中存在执行时间超过10秒的长事务(如事务内包含外部接口调用、大量数据循环插入),导致其他事务等待锁超时,且Undo Log占用磁盘空间急剧增长。

原因分析:
  • 长事务会长期持有行锁,阻塞其他事务的修改操作;
  • 长事务未提交前,Undo Log无法被清理,导致磁盘空间溢出。
解决方案:
  1. 拆分长事务:将事务内的“非数据库操作”(如接口调用、日志记录)移出事务,仅保留核心SQL操作;

    -- 优化前:长事务(包含接口调用)
    BEGIN;
    INSERT INTO orders ...; -- 数据库操作
    call external_payment_api(); -- 外部接口调用(可能耗时5秒)
    UPDATE products SET stock=stock-1 ...; -- 数据库操作
    COMMIT; -- 总耗时可能超过10秒-- 优化后:拆分事务,接口调用移出
    -- 1. 先执行数据库事务(快速提交)
    BEGIN;
    INSERT INTO orders ...;
    UPDATE products SET stock=stock-1 ...;
    COMMIT; -- 耗时<100ms,快速释放锁-- 2. 再执行外部接口调用(非事务内)
    call external_payment_api();
    -- 3. 接口调用失败时,通过“补偿逻辑”处理(如恢复库存)
    IF api_result = 'fail' THENBEGIN;UPDATE products SET stock=stock+1 ...; -- 恢复库存COMMIT;
    END IF;
    
  2. 配置Undo Log自动清理:开启innodb_undo_log_truncate,并设置合理的innodb_undo_tablespaces,避免Undo Log无限增长;

    # my.cnf配置
    innodb_undo_log_truncate = ON
    innodb_undo_tablespaces = 2 # 独立Undo表空间,便于管理
    innodb_max_undo_log_size = 1G # 单个Undo Log文件最大大小,超过则截断
    
  3. 监控长事务:通过information_schema.INNODB_TRX表监控长事务,超过阈值(如30秒)则主动终止;

    -- 查询执行时间超过30秒的事务
    SELECT trx_id, trx_started, trx_duration_ms
    FROM information_schema.INNODB_TRX
    WHERE trx_duration_ms > 30000;-- 终止长事务(需谨慎,避免业务数据不一致)
    KILL trx_id;
    

4.2 问题2:锁等待超时(Lock wait timeout exceeded)

现象:业务日志中频繁出现“Lock wait timeout exceeded; try restarting transaction”错误,尤其在高并发场景(如秒杀、促销)中,事务执行成功率骤降。

原因分析:
  • 多个事务同时修改同一行数据(如扣减同一商品的库存),导致行锁竞争;
  • 事务执行时间过长,持有锁的时间超过innodb_lock_wait_timeout配置的阈值(默认50秒)。
解决方案:
  1. 缩短锁持有时间:优化事务内SQL的执行效率(如加索引避免全表扫描),确保事务快速提交;

    -- 优化前:无索引导致全表扫描,锁持有时间长
    BEGIN;
    UPDATE products SET stock=stock-1 WHERE product_name='iPhone 15'; -- 全表扫描,耗时2秒
    COMMIT;-- 优化后:给product_name加索引,快速定位数据
    ALTER TABLE products ADD INDEX idx_product_name (product_name);
    BEGIN;
    UPDATE products SET stock=stock-1 WHERE product_name='iPhone 15'; -- 索引扫描,耗时<10ms
    COMMIT;
    
  2. 调整锁等待超时时间:根据业务场景缩短innodb_lock_wait_timeout(如设为5-10秒),避免事务长期等待;

    # my.cnf配置(全局生效)
    innodb_lock_wait_timeout = 5# 或会话级临时调整(仅当前会话生效)
    SET SESSION innodb_lock_wait_timeout = 5;
    
  3. 使用乐观锁替代悲观锁:高并发场景下,用“版本号”或“时间戳”实现乐观锁,避免行锁竞争;

    -- 乐观锁实现:通过version字段控制,无需加行锁
    BEGIN;
    -- 1. 查询商品信息,获取当前version
    SELECT stock, version FROM products WHERE product_id=1 FOR UPDATE; -- 此处可改为普通查询,减少锁竞争
    -- 2. 扣库存时校验version是否一致(确保期间无其他事务修改)
    UPDATE products 
    SET stock=stock-1, version=version+1 
    WHERE product_id=1 AND version=#{current_version};
    -- 3. 校验影响行数,若为0说明版本已变,回滚重试
    IF ROW_COUNT() = 0 THENROLLBACK;RETURN '并发修改,请重试';
    END IF;
    COMMIT;
    

4.3 问题3:并发超卖(库存为负数)

现象:秒杀活动中,商品库存出现负数(如库存100,最终卖出105件),违反业务一致性规则,属于严重的事务并发问题。

原因分析:
  • 未在事务中做“库存校验+扣减”的原子操作,导致多个事务同时读取到相同的库存值,进而超卖;
  • 示例:事务A和事务B同时读取到库存=10,均执行stock=stock-1,最终库存=9,而非8,导致多卖1件。
解决方案:
  1. 在UPDATE语句中内置库存校验:将“库存查询+扣减”合并为一条UPDATE语句,利用InnoDB的行锁实现原子操作;

    -- 正确写法:UPDATE语句中加入stock>0的校验,确保扣减后库存不为负
    BEGIN;
    UPDATE products 
    SET stock = stock-1 
    WHERE product_id=1 AND stock > 0; -- 仅当库存>0时才扣减
    -- 检查影响行数,若为0说明库存不足
    IF ROW_COUNT() = 0 THENROLLBACK;RETURN '库存不足';
    END IF;
    -- 插入订单记录
    INSERT INTO orders ...;
    COMMIT;
    
  2. 使用SELECT … FOR UPDATE加行锁:在查询库存时加行锁,避免其他事务同时读取库存值;

    BEGIN;
    -- 加行锁查询库存,其他事务需等待锁释放
    SELECT stock FROM products WHERE product_id=1 FOR UPDATE;
    -- 校验库存
    IF stock <= 0 THENROLLBACK;RETURN '库存不足';
    END IF;
    -- 扣减库存
    UPDATE products SET stock=stock-1 WHERE product_id=1;
    INSERT INTO orders ...;
    COMMIT;
    
  3. 使用Redis预扣库存(高并发场景):秒杀场景下,先在Redis中预扣库存(性能高,支持百万级并发),再异步同步到MySQL,避免直接操作MySQL导致的锁竞争;

    # 伪代码:Redis预扣库存逻辑
    def seckill(product_id, user_id):# 1. Redis中预扣库存(原子操作)stock = redis.decr(f"product_stock:{product_id}")if stock < 0:# 库存不足,回滚Redisredis.incr(f"product_stock:{product_id}")return "库存不足"# 2. 异步同步到MySQL(如通过消息队列)message_queue.send("sync_stock", product_id=product_id)# 3. 创建订单create_order(product_id, user_id)return "秒杀成功"
    

五、事务优化实战:从配置到代码的全链路优化

除了解决典型问题,还需从“配置、SQL、业务逻辑”三个层面进行全链路优化,确保事务在高并发场景下既安全又高效。

5.1 配置优化:平衡安全性与性能

根据业务场景调整事务相关配置,核心原则是“核心业务优先保障安全性,非核心业务优先保障性能”:

业务类型核心配置建议说明
金融交易(核心)transaction_isolation=REPEATABLE-READinnodb_flush_log_at_trx_commit=1innodb_lock_wait_timeout=10完全保障ACID,避免数据丢失,缩短锁等待时间
电商秒杀(高并发)transaction_isolation=READ-COMMITTEDinnodb_flush_log_at_trx_commit=2innodb_lock_wait_timeout=5牺牲部分隔离性(允许不可重复读),提升并发性能,减少锁等待
日志统计(非核心)transaction_isolation=READ-COMMITTEDinnodb_flush_log_at_trx_commit=0autocommit=ON关闭显式事务,使用自动提交,最大化性能

5.2 SQL优化:减少事务内的IO与计算

  1. 事务内只包含必要SQL:避免在事务内执行SELECT查询(可提前查询)、日志打印、循环计算等非必要操作;
  2. 使用索引减少扫描行数:事务内的UPDATE/DELETE语句必须加索引,避免全表扫描导致锁持有时间过长;
  3. 批量操作替代循环操作:批量插入/更新数据(如INSERT INTO ... VALUES (...), (...), (...)),减少事务数量;
    -- 优化前:循环插入100条数据,开启100个事务
    FOR i IN 1..100 LOOPBEGIN;INSERT INTO orders (order_id, user_id) VALUES (i, 100);COMMIT;
    END LOOP;-- 优化后:批量插入,1个事务搞定
    BEGIN;
    INSERT INTO orders (order_id, user_id) 
    VALUES (1,100), (2,100), ..., (100,100); -- 批量插入
    COMMIT;
    

5.3 业务逻辑优化:避免事务依赖

  1. 拆分“强依赖”与“弱依赖”操作:将“必须原子执行”的操作(如扣库存、创建订单)放入事务,“非必须原子执行”的操作(如发送短信、推送通知)移出事务;
  2. 使用补偿机制处理事务失败:事务失败后,通过“补偿逻辑”恢复数据(如库存扣减失败则恢复库存,订单创建失败则删除订单记录),避免依赖事务回滚处理所有场景;
  3. 避免分布式事务:分布式事务(如跨MySQL、Redis、MongoDB的事务)性能差且易出现一致性问题,尽量通过“最终一致性”方案替代(如消息队列异步同步)。

六、总结:事务使用的核心原则

  1. ACID优先,兼顾性能:核心业务(如金融、订单)必须严格保障ACID,非核心业务(如统计、日志)可适当牺牲隔离性或持久性提升性能;
  2. 事务越小越好:事务内只包含核心SQL,避免长事务导致锁等待与Undo Log膨胀;
  3. 索引是事务并发的关键:事务内的修改操作必须加索引,减少锁持有时间,避免全表扫描;
  4. 监控与补偿并重:定期监控长事务、锁等待,建立事务失败后的补偿机制,确保业务最终一致性;
  5. 配置需贴合场景:根据业务类型调整transaction_isolationinnodb_flush_log_at_trx_commit等配置,不盲目追求“最高安全性”或“最高性能”。

InnoDB事务的本质是“在数据安全性与并发性能之间找平衡”——理解ACID的底层实现,掌握典型问题的解决方案,结合业务场景优化配置与代码,才能真正发挥事务的价值,保障业务数据的一致性与稳定性。


Studying will never be ending.

▲如有纰漏,烦请指正~~

http://www.dtcms.com/a/456772.html

相关文章:

  • 重庆江津网站建设企业专业网站设计公
  • 天津市武清区住房建设网站临沂天元建设集团网站
  • MySQL 锁机制深度解析:原理、场景、排查与优化​
  • Spring 的统一功能
  • 忘记php网站后台密码wordpress 医院模板下载
  • asp 网站卡死网站域名解析ip
  • Linux小课堂: 在 VirtualBox 虚拟机中安装 CentOS 7 的完整流程与关键技术详解
  • 单片机keilC51与MDK共存的方法(成功)
  • [Docker集群] Docker 容器入门
  • 分子动力学--不同拮抗剂与5-HT1AR结合机制的研究:一项分子对接与分子动力学模拟分析
  • 让压测回归简单:体验 PerfTest 分布式模式的“开箱即用”
  • 珠海网站制作定制企查查企业信息查询网页版
  • ZooKeeper源码分析与实战-模块五:原理篇
  • ZooKeeper源码分析与实战-模块四:实战篇
  • 元宇宙的医疗健康应用:重构诊疗、康复与研究
  • 建设外贸购物网站如何在网站做引流
  • 珠宝网站策划书做网站怎么赚钱吗
  • K-means损失函数-收敛证明
  • 如何看网站是不是织梦做的建一家网站多少钱
  • 通讯录的实现
  • CTFHub SQL注入通关笔记5:时间盲注(手注法+脚本法)
  • Excel表格批注提取器-网页版源码
  • 【机器学习】无监督学习 —— K-Means 聚类、DBSCAN 聚类
  • 【深入浅出PyTorch】--3.2.PyTorch组成模块2
  • [C++] --- 常用设计模式
  • vite 怎么阻止某一页面的热更新
  • 邯郸网站设计做网站的一般尺寸
  • 【Linux系列】并发世界的基石:透彻理解 Linux 进程 — 进程优先级切换调度
  • 上海做网站技术做海报找素材网站
  • 全志 H3 armbian 备份