告别SQL“龟速”:Java程序员的深度排查与优化指南

📃个人主页:小韩学长yyds-优快云博客

⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏 💞 💞 💞

箴言:拥有耐心才是生活的关键

目录

慢 SQL:不容忽视的性能杀手

慢 SQL 的排查之道

(一)日志分析:开启 MySQL 慢查询日志

(二)执行计划剖析:使用 Explain 工具

(三)性能监控工具助力:Performance Schema 与第三方工具

慢 SQL 的解决之法

(一)SQL 语句优化

1. 索引优化:分析索引失效的常见原因,如字段类型不匹配、函数使用不当等,通过创建合适的索引、避免索引失效来提高查询性能。

2. 查询语句重写:避免使用通配符、减少子查询、用 JOIN 代替嵌套 SELECT 等,优化查询语句结构,提升执行效率。

3. 分页优化:针对 LIMIT 分页的性能问题,介绍优化分页查询的方法,如利用上一页最大值作为查询参数、使用书签分页等。

(二)数据库结构优化

1. 垂直分割表:阐述垂直分割表的原理和适用场景,通过将表按字段功能拆分,减少数据冗余,提高查询性能。

2. 水平分割表:介绍水平分割表的概念和实现方式,在处理大数据量时,通过按数据行拆分表,降低单表数据量,提升查询效率。

(三)服务器参数调整

1. 内存优化:调整 innodb_buffer_pool_size 参数,增加 InnoDB 存储引擎的缓存内存大小,提高数据读取速度。

2. 并发连接数优化:根据服务器负载情况,合理调整 MySQL 的最大并发连接数 max_connections,避免连接数过多或过少导致的性能问题。

实践案例分析


慢 SQL:不容忽视的性能杀手

在当今数字化时代,应用程序的性能犹如生命线,直接关乎用户体验和业务的成败。想象一下,当你满心期待地在电商 APP 上搜索心仪的商品,页面却迟迟不加载;或者在办公系统中提交一份紧急报告,点击保存后却陷入漫长的等待…… 这些场景是不是让人抓狂?而背后的罪魁祸首,很可能就是慢 SQL。

SQL(Structured Query Language)作为与数据库交互的核心语言,承担着数据查询、插入、更新和删除等关键操作。当 SQL 语句执行缓慢时,数据库需要花费更多的时间和资源来处理请求,这不仅导致应用程序响应迟缓,还会使数据库服务器的负载急剧上升。

从用户体验的角度来看,慢 SQL 就像一个隐形的敌人,悄无声息地侵蚀着用户的耐心。在这个快节奏的时代,用户对于响应速度的容忍度越来越低。根据调查显示,网页加载时间每增加一秒,用户流失率可能会上升 7%。而在移动应用中,这种影响更为显著。一个响应缓慢的应用,很容易被用户抛弃,转而选择竞争对手的产品。这对于企业来说,意味着潜在客户的流失和市场份额的下降。

从系统性能的角度分析,慢 SQL 会消耗大量的数据库资源,如 CPU、内存和磁盘 I/O。当数据库忙于处理这些缓慢的查询时,其他正常的请求可能会被阻塞,导致整个系统的吞吐量下降。在高并发的场景下,这种问题会被进一步放大,甚至可能引发数据库服务器的崩溃,造成严重的业务中断。例如,在电商大促期间,如果订单查询或支付处理的 SQL 语句出现性能问题,可能会导致大量用户无法正常下单或支付,给企业带来巨大的经济损失。

慢 SQL 的排查之道

面对慢 SQL 这个性能杀手,我们不能坐以待毙,必须主动出击,掌握有效的排查方法。就像医生诊断疾病一样,我们需要借助各种工具和技术,深入分析数据库的运行状况,找出慢 SQL 的根源。下面,我将为大家详细介绍排查慢 SQL 的实用方法。

(一)日志分析:开启 MySQL 慢查询日志

MySQL 慢查询日志就像是数据库的 “黑匣子”,它默默地记录着所有执行时间超过设定阈值的 SQL 语句。通过分析这些日志,我们能够直观地了解到哪些查询语句执行缓慢,以及它们的执行时间、扫描行数等关键信息 。

开启 MySQL 慢查询日志的方法并不复杂。我们可以通过修改 MySQL 的配置文件(通常是 my.cnf 或 my.ini)来实现。在配置文件中,找到或添加以下配置项:

 
 

slow_query_log = ON

slow_query_log_file = /var/log/mysql/slow-query.log

long_query_time = 2

log_queries_not_using_indexes = ON

上述配置中,slow_query_log 用于开启慢查询日志;slow_query_log_file 指定了日志文件的存储路径;long_query_time 设置了慢查询的时间阈值,这里表示执行时间超过 2 秒的 SQL 语句将被记录;log_queries_not_using_indexes 则表示将未使用索引的查询也记录到慢查询日志中。修改完配置文件后,重启 MySQL 服务,慢查询日志就开始生效了。

当我们查看慢查询日志时,会看到类似下面这样的记录:

 
 

# Time: 230717 14:35:47

# User@Host: root[root] @ localhost []

# Query_time: 3.001234 Lock_time: 0.000123 Rows_sent: 100 Rows_examined: 10000

SET timestamp=1689604547;

SELECT * FROM users WHERE age > 30;

从这条记录中,我们可以清晰地获取到查询执行的时间(Query_time)、锁定时间(Lock_time)、返回的行数(Rows_sent)以及扫描的行数(Rows_examined)等信息。这些信息对于我们分析查询性能非常有帮助。

为了更高效地分析慢查询日志,我们还可以借助一些工具,如mysqldumpslow。这是 MySQL 自带的一个日志分析工具,它可以对慢查询日志进行汇总和分析,帮助我们快速找出最耗时的 SQL 语句。例如,使用以下命令可以获取按查询时间排序的前 10 条慢 SQL:

mysqldumpslow -s t -t 10 /var/log/mysql/slow-query.log

(二)执行计划剖析:使用 Explain 工具

如果说慢查询日志让我们知道了哪些 SQL 语句执行缓慢,那么 Explain 工具则像是一把手术刀,帮助我们深入剖析 SQL 语句的执行过程,找出性能瓶颈所在。

使用 Explain 工具非常简单,只需在 SQL 语句前加上EXPLAIN关键字即可。例如:

EXPLAIN SELECT * FROM orders WHERE order_date > '2023-01-01';

执行上述语句后,MySQL 会返回一个执行计划,包含以下关键信息:

  • id:每个 SELECT 都对应一个 id,从 1 开始递增。如果该查询有子查询,将显示多个 id 值。id 相同执行顺序从上往下,id 不同序号大的先执行。同时存在时,先执行序号大的,再从上往下。NULL 最后执行,且表示结果集,不需要使用它进行查询 。
  • select_type:表示对应行是简单还是复杂的查询。常见的类型有SIMPLE(简单查询,不包含 union 与子查询)、PRIMARY(复杂查询中最外层查询)、SUBQUERY(在 select 语句中出现的查询语句,结果不依赖于外部查询)、DERIVED(在 FROM 子句的查询语句,标识从外部数据源中推导出来的)、UNION(在 union 中的第二个和随后的 select)等 。
  • table:查询所涉及的表名。如果有别名,展示别名。当 from 子句中有子查询时,table 列是<derivedN>格式,表示当前查询依赖 id=N 的查询,于是先执行 id=N 的查询 。
  • type:查询访问类型,从最优到最差分别为system>const>eq_ref>ref>range>index>ALL。一般来说,得保证查询达到 range 级别,最好达到 ref。ALL表示全表扫描,性能最差;ref表示使用普通索引进行等值匹配;eq_ref表示使用唯一索引进行等值匹配,性能较好 。
  • possible_keys:表示在查询中可能使用到某个索引或多个索引,如果没有选择索引,显示 NULL 。
  • key:表示在实际在查询中使用的索引,如果没有使用索引,则该列是 NULL 。
  • key_len:显示了 mysql 在索引里使用的字节数,通过这个值可以算出具体使用了索引中的哪些列 。
  • ref:显示了在 key 列记录的索引中,表查找值所用到的列或常量 。
  • rows:mysql 估计要读取并检测的行数,注意这个不是结果集里的行数 。
  • Extra:展示的是额外信息。常见的重要值有Using index(使用覆盖索引)、Using where(使用 where 语句来处理结果,查询的列未被索引覆盖)、Using temporary(mysql 需要创建一张临时表来处理查询,出现这种情况一般需要优化)、Using filesort(将用外部排序而不是索引排序,数据较小时从内存排序,否则需要在磁盘完成排序,这种情况一般也需要使用索引来优化)等 。

通过分析执行计划中的这些信息,我们可以判断 SQL 语句是否使用了合适的索引,是否存在全表扫描等性能问题。例如,如果type为ALL,说明查询进行了全表扫描,可能需要添加索引来优化;如果Extra中出现Using temporary和Using filesort,则表示查询需要创建临时表和进行文件排序,这也会导致性能下降,需要进一步优化查询语句。

(三)性能监控工具助力:Performance Schema 与第三方工具

除了慢查询日志和 Explain 工具,我们还可以借助一些性能监控工具来实时监控 SQL 的执行情况,及时发现慢 SQL。

MySQL 自带的 Performance Schema 是一个强大的性能监控工具,它可以在数据库运行时实时检查 Server 的内部执行情况,收集各种性能数据,如查询计数、锁定状态、I/O 操作等。Performance Schema 数据库包含了许多表,用于存储不同类型的性能数据 。

例如,performance_schema.events_statements_summary_by_digest表记录了执行的语句摘要信息,包括执行次数、平均执行时间等;performance_schema.events_waits_summary_global_by_event_name表记录了等待事件的摘要信息,如锁等待时间、IO 等待时间等。通过查询这些表,我们可以获取到 SQL 语句的详细执行信息,从而找出性能瓶颈。

要使用 Performance Schema,首先需要确保它在 MySQL 中已经启用。在 MySQL 5.7 及之后的版本中,Performance Schema 默认是启用的。我们可以通过以下语句来查看其状态:

SHOW VARIABLES LIKE 'performance_schema';

如果Value为ON,则表示已经启用。

除了 Performance Schema,还有许多优秀的第三方监控工具可供选择,如 Prometheus + Grafana、Percona Monitoring and Management (PMM)、Lepus 天兔等。这些工具通常提供了更直观的界面和更丰富的功能,可以帮助我们更方便地监控和分析数据库性能。

Prometheus + Grafana 为例,Prometheus 是一个开源的系统监控和告警工具,它可以从 MySQL 中采集各种性能指标,如查询响应时间、连接数、慢查询次数等。Grafana 则是一个用于可视化监控数据的开源平台,它可以将 Prometheus 采集到的数据以图表的形式展示出来,让我们更直观地了解数据库的运行状态。通过配置 Prometheus 和 Grafana,我们可以创建一个强大的 MySQL 监控系统,实时监控 SQL 的执行情况,及时发现并解决慢 SQL 问题。

慢 SQL 的解决之法

当我们通过前面的排查方法找出慢 SQL 后,接下来就需要对症下药,采取有效的解决措施。解决慢 SQL 问题通常可以从 SQL 语句优化数据库结构优化和服务器参数调整这几个方面入手。

(一)SQL 语句优化

SQL 语句是与数据库交互的直接指令,其编写的质量直接影响着查询性能。下面我们将从索引优化、查询语句重写和分页优化这三个关键方面来探讨如何提升 SQL 语句的执行效率。

1. 索引优化:分析索引失效的常见原因,如字段类型不匹配、函数使用不当等,通过创建合适的索引、避免索引失效来提高查询性能。

索引就像是书籍的目录,能够帮助数据库快速定位到所需的数据。然而,在实际应用中,索引可能会因为各种原因失效,导致查询性能大幅下降。

字段类型不匹配是导致索引失效的常见原因之一。例如,如果数据库表中的某个字段定义为VARCHAR类型,而在查询时却将其与INT类型的值进行比较,数据库就需要进行隐式类型转换,这可能会使索引无法正常使用。假设我们有一个users表,其中user_id字段为VARCHAR类型,当执行以下查询时:

SELECT * FROM users WHERE user_id = 123;

由于user_id是VARCHAR类型,而查询值123是INT类型,索引就会失效。正确的做法是将查询值用引号括起来,使其类型与字段类型一致:

SELECT * FROM users WHERE user_id = '123';

函数使用不当也会导致索引失效。当在查询条件中对字段使用函数时,数据库无法直接利用索引进行查找,而是需要对每一行数据进行函数计算后再进行比较,这大大增加了查询的时间复杂度。比如,使用UPPER函数查询用户表中用户名大写为'ALICE'的记录:

SELECT * FROM users WHERE UPPER(username) = 'ALICE';

这样的查询会使username字段上的索引失效。为了避免这种情况,我们可以在应用层进行字符串转换,或者在数据库中创建函数索引。

创建合适的索引是提高查询性能的关键。在创建索引时,我们需要根据实际的查询需求来选择合适的字段和索引类型。对于经常在WHERE子句中使用的字段,应该考虑创建索引。如果查询条件经常涉及多个字段的组合,那么创建复合索引可能会更有效。例如,在一个orders表中,经常根据customer_id和order_date进行查询,我们可以创建如下复合索引:

CREATE INDEX idx_customer_date ON orders (customer_id, order_date);

需要注意的是,复合索引遵循最左前缀原则,即只有在查询条件中使用了复合索引的最左边的字段时,索引才会被有效利用。

2. 查询语句重写:避免使用通配符、减少子查询、用 JOIN 代替嵌套 SELECT 等,优化查询语句结构,提升执行效率。

查询语句的结构对执行效率有着重要影响。一些看似简单的查询语句,可能隐藏着性能问题。我们应该尽量避免使用通配符,特别是在通配符出现在字符串开头的情况。例如,使用LIKE '%keyword'进行查询时,数据库无法利用索引,只能进行全表扫描。如果必须使用模糊查询,可以考虑使用LIKE 'keyword%',这样可以利用索引提高查询效率。或者使用INSTR函数代替LIKE操作符进行模糊匹配,INSTR函数不会识别通配符,只会判断第二个字符串参数在第一个字符串中是否存在 。

子查询虽然在某些情况下可以实现复杂的查询逻辑,但过多的子查询会增加查询的复杂度和执行时间。我们可以尝试将子查询转换为 JOIN 操作,以提高查询性能。例如,有两个表orders和customers,原本使用子查询查询每个订单对应的客户信息:

 
 

SELECT * FROM orders WHERE customer_id IN (SELECT customer_id FROM customers WHERE region = 'Asia');

可以改写为 JOIN 操作:

 
 

SELECT orders.*, customers.* FROM orders JOIN customers ON orders.customer_id = customers.customer_id WHERE customers.region = 'Asia';

JOIN 操作可以减少数据库的查询次数,提高执行效率。

3. 分页优化:针对 LIMIT 分页的性能问题,介绍优化分页查询的方法,如利用上一页最大值作为查询参数、使用书签分页等。

在实现分页功能时,LIMIT子句是常用的方法。然而,当数据量较大时,LIMIT分页会出现性能问题,特别是在偏移量较大的情况下。例如,SELECT * FROM users LIMIT 10000, 10,数据库需要先扫描前 10000 条记录,然后再返回后面的 10 条记录,这会消耗大量的时间和资源。

为了优化分页查询,我们可以利用上一页的最大值作为查询参数。假设users表中有一个user_id字段,且该字段是唯一且自增的。在上一页查询的结果中,我们可以获取到最后一条记录的user_id,然后在下一页查询时,以此为条件进行查询:

 
 

SELECT * FROM users WHERE user_id > 10000 LIMIT 10;

这样,数据库只需要从user_id大于 10000 的记录中获取 10 条,大大减少了扫描的数据量。

另一种优化方法是使用书签分页。书签分页是通过记录当前页的某个唯一标识(如user_id)作为书签,在下一页查询时,根据书签来定位数据。这种方法可以避免偏移量过大带来的性能问题,提高分页查询的效率。

(二)数据库结构优化

除了优化 SQL 语句,合理设计数据库结构也是提高查询性能的重要手段。数据库结构优化主要包括垂直分割表和水平分割表。

1. 垂直分割表:阐述垂直分割表的原理和适用场景,通过将表按字段功能拆分,减少数据冗余,提高查询性能。

垂直分割表是将一个表中的列按照功能或使用频率进行拆分,将相关的列放在一个子表中。例如,在一个用户信息表users中,可能包含用户的基本信息(如user_id、username、age)和详细信息(如address、phone_number、email)。如果大部分查询只需要获取用户的基本信息,而详细信息很少被查询,我们可以将基本信息和详细信息分别存放在两个子表中,即users_basic和users_detail。这样,在查询基本信息时,只需要扫描users_basic表,减少了数据扫描量,提高了查询性能。

-- 创建基本信息表

CREATE TABLE users_basic (

user_id INT PRIMARY KEY,

username VARCHAR(50),

age INT

);

-- 创建详细信息表

CREATE TABLE users_detail (

user_id INT PRIMARY KEY,

address VARCHAR(200),

phone_number VARCHAR(20),

email VARCHAR(100),

FOREIGN KEY (user_id) REFERENCES users_basic(user_id)

);

垂直分割表适用于表中某些列经常被查询,而其他列很少被查询的场景。通过垂直分割,可以减少表的宽度,降低 I/O 操作,提高查询效率。

2. 水平分割表:介绍水平分割表的概念和实现方式,在处理大数据量时,通过按数据行拆分表,降低单表数据量,提升查询效率。

水平分割表是将一个表按照数据行进行拆分,将不同的数据行存放在不同的子表中。例如,在一个订单表orders中,随着业务的发展,数据量越来越大。为了提高查询性能,我们可以按照订单时间将数据进行水平分割,将不同时间段的订单存放在不同的子表中,如orders_202301、orders_202302等。

-- 创建2023年1月订单表

CREATE TABLE orders_202301 (

order_id INT PRIMARY KEY,

customer_id INT,

order_date DATE,

total_amount DECIMAL(10, 2)

);

-- 创建2023年2月订单表

CREATE TABLE orders_202302 (

order_id INT PRIMARY KEY,

customer_id INT,

order_date DATE,

total_amount DECIMAL(10, 2)

);

在查询时,根据订单时间选择对应的子表进行查询,从而减少单表的数据量,提高查询效率。水平分割表适用于数据量非常大,单表无法满足性能要求的场景。通过水平分割,可以将数据分布到多个表中,降低单个表的负载,提升系统的整体性能。

(三)服务器参数调整

服务器参数的合理配置对于数据库性能也有着重要影响。我们可以通过调整内存参数和并发连接数参数来优化数据库的运行环境。

1. 内存优化:调整 innodb_buffer_pool_size 参数,增加 InnoDB 存储引擎的缓存内存大小,提高数据读取速度。

innodb_buffer_pool_size是 InnoDB 存储引擎的一个重要参数,它用于设置缓存内存的大小。InnoDB 会将数据和索引缓存到这个内存区域中,当进行查询时,如果所需的数据已经在缓存中,就可以直接从内存中读取,而不需要从磁盘中读取,从而大大提高数据读取速度。

一般来说,我们可以根据服务器的内存大小和数据库的工作负载来调整innodb_buffer_pool_size参数。对于内存充足且读密集型的数据库,可以将该参数设置得较大,以提高缓存命中率。在 MySQL 配置文件(通常是my.cnf或my.ini)中,找到或添加以下配置项来设置innodb_buffer_pool_size:

 
 

[mysqld]

innodb_buffer_pool_size = 2G

上述配置将innodb_buffer_pool_size设置为 2GB。需要注意的是,设置该参数时要确保服务器有足够的内存供其他系统进程使用,一般建议缓冲池大小小于服务器总内存的 70% - 80%。同时,对于大型缓冲池,可以考虑将缓冲池分成多个实例(通过innodb_buffer_pool_instances参数配置),以减少锁争用并提高并发性能 。

2. 并发连接数优化:根据服务器负载情况,合理调整 MySQL 的最大并发连接数 max_connections,避免连接数过多或过少导致的性能问题。

max_connections参数用于设置 MySQL 允许的最大并发连接数。当应用程序与数据库建立连接时,如果并发连接数超过了max_connections,新的连接请求将被拒绝。如果max_connections设置得过小,在高并发场景下,可能会导致大量连接请求被拒绝,影响应用程序的正常运行;而如果设置得过大,可能会导致服务器资源耗尽,因为每个连接都需要占用一定的系统资源(如内存)。

我们可以通过查看 MySQL 的状态信息(如Threads_connected)来了解当前的并发连接数情况,并根据服务器的负载情况来调整max_connections参数。在 MySQL 配置文件中,添加或修改以下配置项:

 
 

[mysqld]

max_connections = 1000

上述配置将最大并发连接数设置为 1000。在实际应用中,需要根据服务器的硬件配置、数据库的工作负载以及应用程序的并发访问量等因素来综合考虑,找到一个合适的max_connections值。同时,还可以结合连接池技术(如 Druid、HikariCP 等)来管理数据库连接,提高连接的复用率,减少连接创建和销毁的开销。

实践案例分析

为了让大家更深入地理解慢 SQL 的排查和解决过程,下面我将分享一个实际项目中遇到的慢 SQL 问题案例。

在一个电商项目中,用户反馈商品搜索页面加载速度极慢,有时甚至需要等待十几秒才能显示结果。这严重影响了用户体验,导致用户流失率上升。作为开发团队的一员,我接到了排查和解决这个问题的任务。

首先,我开启了 MySQL 的慢查询日志,发现一条查询商品列表的 SQL 语句执行时间长达 8 秒。这条 SQL 语句如下:

 
 

SELECT * FROM products WHERE category = 'electronics' AND price BETWEEN 100 AND 500 ORDER BY rating DESC LIMIT 10;

从表面上看,这条语句似乎很简单,只是根据商品类别和价格范围查询商品,并按评分排序返回前 10 条记录。但为什么会执行这么慢呢?

接下来,我使用 Explain 工具分析了这条 SQL 语句的执行计划:

 
 

EXPLAIN SELECT * FROM products WHERE category = 'electronics' AND price BETWEEN 100 AND 500 ORDER BY rating DESC LIMIT 10;

执行计划的结果显示:

 

+----+-------------+----------+------+---------------+------+---------+------+------+----------+

| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |

+----+-------------+----------+------+---------------+------+---------+------+------+----------+

| 1 | SIMPLE | products | ALL | NULL | NULL | NULL | NULL | 1000 | Using where; Using filesort |

+----+-------------+----------+------+---------------+------+---------+------+------+----------+

从执行计划中可以看出,type为ALL,表示进行了全表扫描;Extra中出现了Using where和Using filesort,说明查询使用了 where 语句来处理结果,并且进行了文件排序。这就是导致查询缓慢的主要原因。

进一步分析发现,products表中没有针对category、price和rating字段的索引,这使得数据库在查询时无法利用索引快速定位数据,只能进行全表扫描。同时,由于需要对查询结果进行排序,数据库又进行了一次文件排序操作,这进一步增加了查询的时间开销。

针对这些问题,我采取了以下解决措施:

  1. 创建复合索引:根据查询条件,创建一个包含category、price和rating字段的复合索引,以提高查询效率。
 
 

CREATE INDEX idx_category_price_rating ON products (category, price, rating);

  2. 优化查询语句:将查询语句中的SELECT *改为只查询需要的字段,减少数据传输量。

 
 

SELECT product_id, product_name, price, rating FROM products WHERE category = 'electronics' AND price BETWEEN 100 AND 500 ORDER BY rating DESC LIMIT 10;

修改完后,再次使用 Explain 工具分析执行计划:

 
 

EXPLAIN SELECT product_id, product_name, price, rating FROM products WHERE category = 'electronics' AND price BETWEEN 100 AND 500 ORDER BY rating DESC LIMIT 10;

新的执行计划结果显示:

 

+----+-------------+----------+------+----------------------------------+----------------------------------+---------+------+------+-------------+

| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |

+----+-------------+----------+------+----------------------------------+----------------------------------+---------+------+------+-------------+

| 1 | SIMPLE | products | range | idx_category_price_rating | idx_category_price_rating | 170 | NULL | 100 | Using where |

+----+-------------+----------+------+----------------------------------+----------------------------------+---------+------+------+-------------+

可以看到,type变为range,表示使用了索引范围扫描;Extra中只有Using where,不再有Using filesort,说明查询性能得到了显著提升。

经过上述优化后,再次执行查询语句,执行时间从原来的 8 秒缩短到了 0.1 秒,商品搜索页面的加载速度得到了极大的改善,用户体验也得到了显著提升。

通过这个案例,我们可以看到,慢 SQL 问题的排查和解决需要综合运用各种工具和技术,深入分析问题的根源,并采取针对性的优化措施。只有这样,才能有效地提升系统性能,为用户提供更加流畅的使用体验。

结语

🔥如果此文对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏、✍️评论,支持一下博主~ 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值