SQL优化


1.说明:
    数据库系统需要保存大量历史记录,系统内存在许多历史记录表,因此常常出现系统运行一段时间,表记录数达到一定数量后,系统响应明显变慢的现象。为尽可能的提高 SQL 执行的效率,我们在编写 SQL 语句应该遵循一定的优化规则,使代码风格统一、规范。充分利用表索引,避免进行全表扫描;充分利用结构化编程方式,提高查询的复用能力,也许完全遵守以下方法速度未必达到想要的结果,但是养成一个好的编程习惯是重要的。

2.调尤方法

2.1. 相同功能、性能的SQL语句

   ORACLE 采用共享内存 SGA 的机制,因此 ORACLE 会对每个不同写法的 SQL 进行分析,并且占用共享内存 ; 如果书写格式完全相同,则 ORACLE 只分析一次,遇到相同书写格式的 SQL ,会直接从共享内存中获取结果集 ; 这样便能减少共享池的开销以及代码的复用。
处理方法:保证书写格式相同,包括大小写,空格位置,表别名等一致;将一些通用的 SQL 语句作为公共函数由其它函数调用的方式使用。
2.2. 动态SQL:动态SQL采用变量动态绑定的方式,避免重复解析。2.3.连接方式与表名顺序
多表查询时需要选择最有效率的表名顺序(基于规则的优化器有效), ORACLE 的解析器按照从右到左的顺序处理 FROM 子句中的表名;因此写在最后的那张基础表最先被处理,即选择记录数最少的表作为基础表,首先 , 扫描 FROM 子句中最右的那个表,并对记录进行排序 , 然后扫描 FROM 子句中最后第二个表 , 最后将所有从第二个表中检索出的记录与第一个表中合适记录进行合并。
2.4.查询条件顺序
ORACLE 采用自下而上的顺序解析 WHERE 子句,根据这个原理 , 表之间的连接必须写在其他 WHERE 条件之前, 那些可以过滤掉最大数量记录的条件适合写在 WHERE 子句的末尾。
2.5. 语法和语义2.5.1.SELECT子句中避免使用' * '
使用 '*' ,Oracle 便会查询数据字典,依次解析各列名,应明确指定各列的名称,这样也便于理解。
Select * from t_user t;
2.5.2. 使用表的别名(Alias)
多表查询时,使用表的别名,同样可以避免解析,避免歧义,提高效率。
2.5.3. 常量优化:
常量的计算是在语句被优化时一次性完成,而不是在每次执行时。下面是检索月薪大于 2000 的的表达式:
sal > 24000/12
sal > 2000
sal*12 > 24000
如果 SQL 语句包括第一种情况,优化器会简单地把它转变成第二种。
优化器不会简化跨越比较符的表达式,例如第三条语句,鉴于此,应尽量写用常量跟字段比较检索的表达式,而不要将字段置于表达式当中。否则没有办法优化,比如如果 sal 上有索引,第一和第二就可以使用,第三就难以使用。
2.6. 函数和表达式2.6.1.>=替代>
例如 DEPTNO 上有一个索引 ,
高效 : SELECT * FROM EMP WHERE DEPTNO >=4
低效 :SELECT * FROM EMP WHERE DEPTNO >3
    两者的区别在于,前者 DBMS 将直接跳到第一个 DEPT 等于 4 的记录而后者将首先定位到 DEPTNO=3 的记录并且向前扫描到第一个 DEPT 大于 3 的记录
2.6.2. 使用DECODE函数来减少处理时间
    使用 DECODE 函数可以避免重复扫描相同记录或重复连接相同的表 .
2.6.3. TRUNCATE替代DELETE
    当删除表中的记录时 , 在通常情况下, 回滚段 (rollback segments ) 用来存放可以被恢复的信息 . 如果你没有 COMMIT 事务, ORACLE 会将数据恢复到删除之前的状态 ( 准确地说是恢复到执行删除命令之前的状况 ) ,而当运用 TRUNCATE 时, 回滚段不再存放任何可被恢复的信息 . 当命令运行后,数据不能被恢复,因此很少的资源被调用,执行时间也会很短。 TRUNCATE 只在删除全表或分区适用, TRUNCATE DDL 不是 DML
2.7. 常用操作符优化
2.7.1. LIKE操作符组合没有通配符的表达式
优化器把使用 LIKE 操作符和一个没有通配符的表达式组成的检索表达式转换为一个“ = ”操作符表达式。
例如:优化器会把表达式 ename LIKE 'SMITH' 转换为 ename = 'SMITH'
优化器只能转换涉及到可变长数据类型的表达式,前一个例子中,如果 ENAME 字段的类型是 CHAR(10) , 那么优化器将不做任何转换。
一般来讲 LIKE 比较难以优化。
2.7.2. EXISTS替代INnot exists 替换 not in
    优化器把使用 IN 比较符的检索表达式替换为等价的使用“ = ”和“ OR ”操作符的检索表达式。 用 IN/ not in将启用全表扫描,使用 EXISTS 将利用索引,提高查询效率。 EXISTS 要远比 IN 的效率高。里面关系到 full table scan range scan 。几乎将所有的 IN 操作符子查询改写为使用 EXISTS 的子查询。
    例如,优化器会把表达式 ename IN ('SMITH','KING','JONES') 替换为
ename = 'SMITH' OR ename = 'KING' OR ename = 'JONES
语句 1
SELECT dname, deptno FROM dept
WHERE deptno NOT IN
(SELECT deptno FROM emp);
语句 2
SELECT dname, deptno FROM dept
WHERE NOT EXISTS
(SELECT deptno FROM emp
WHERE dept.deptno = emp.deptno);
2 要比 1 的执行性能好很多。因为 1 中对 emp 进行了 full table scan, 这是很浪费时间的操作。而且 1 中没有用到 emp INdex 因为没有 WHERE 子句。而 2 中的语句对 emp 进行的是 range scan
2.7.3. 用表连接替换EXISTS2.7.4.EXISTS替换DISTINCT2.7.5.避免在索引列上使用IS NULLIS NOT NULL
不能用 null 作索引,任何包含 null 值的列都将不会被包含在索引中。即使索引有多列这样的情况下,只要这些列中有一列含有 null ,该列就会从索引中排除。也就是说如果某列存在空值,即使对该列建索引也不会提高性能。任何在 WHERE 子句中使用 is null is not null 的语句优化器是不允许使用索引的。
举例如下:
低效 ( 索引失效 ) :
SELECT * FROM DEPARTMENT WHERE DEPT_CODE IS NOT NULL;
高效 ( 索引有效 ) : SELECT * FROM DEPARTMENT WHERE DEPT_CODE >=0;
2.7.6. UNION-ALL替换UNION
    当 SQL 语句需要 UNION 两个查询结果集合时,这两个结果集合会以 UNION-ALL 的方式被合并,然后在输出最终结果前进行排序。
    如果用 UNION ALL 替代 UNION ,这样排序就不是必要了。效率就会因此得到提高。
2.7.7. UNION替换OR (适用于索引列)
    如果不产生大量重复值,可以考虑把子句拆开。拆开的子句中应该包含索引。
通常情况下,用 UNION 替换 WHERE 子句中的 OR 将会起到较好的效果。对索引列使用 OR 将造成全表扫描。注意,以上规则只针对多个索引列有效。如果有 column 没有被索引,查询效率可能会因为你没有选择 OR 而降低。
2.7.8. WHERE替代ORDER BY
ORDER BY 子句只在两种严格的条件下使用索引 : ORDER BY 中所有的列必须包含在相同的索引中并保持在索引中的排列顺序。 ORDER BY 中所有的列必须定义为非空。 WHERE 子句使用的索引和 ORDER BY 子句中所使用的索引不能并列 .
2.7.9. WHERE子句替换HAVING子句
避免使用 HAVING 子句, HAVING 只会在检索出所有记录之后才对结果集进行过滤。 这个处理需要排序,总计等操作。如果能通过 WHERE 子句限制记录的数目,那就能减少这方面的开销。例子如下 :
   低效 :
  SELECT REGION AVG(LOG_SIZE)
   FROM LOCATION
   GROUP BY REGION
    HAVING REGION REGION != 'SYDNEY'   AND REGION != 'PERTH'
    高效
    SELECT REGION AVG(LOG_SIZE)
    FROM LOCATION
    WHERE REGION REGION != 'SYDNEY'
    AND REGION != 'PERTH'
    GROUP BY REGION
HAVING 中的条件一般用于对一些集合函数的比较,如 COUNT() 等等。除此之外,一般的条件应该写在 WHERE 子句中。
2.7.10. GROUP BY的使用
提高 GROUP BY 语句的效率,可以通过将不需要的记录在 GROUP BY 之前过滤掉。下面两个查询返回相同结果但第二个明显就快了许多。
2.7.11. BETWEEN操作符:
    优化器总是用“ >= ”和“ <= ”比较符来等价的代替 BETWEEN 操作符。
例如:优化器会把表达式 sal BETWEEN 2000 AND 3000 sal >= 2000 AND sal <= 3000 来代替。
2.7.12. NOT操作符:
    优化器总是试图简化检索条件以消除“ NOT ”逻辑操作符的影响,这将涉及到“ NOT ”操作符的消除以及代以相应的比较运算符。
    例如,优化器将下面的第一条语句用第二条语句代替:
    NOT deptno = (SELECT deptno FROM emp WHERE ename = 'TAYLOR')
    deptno <> (SELECT deptno FROM emp WHERE ename = 'TAYLOR')
    通常情况下一个含有 NOT 操作符的语句有很多不同的写法,优化器的转换原则是使“ NOT ”操作符后边的子句尽可能的简单,即使可能会使结果表达式包含了更多的“ NOT ”操作符。
    例如,优化器将如下所示的第一条语句用第二条语句代替:
    NOT (sal < 1000 OR comm IS NULL)
    NOT sal < 1000 AND comm IS NOT NULL sal >= 1000 AND comm IS NOT NULL
2.7.13. ANYSOME操作符优化:
    优化器将跟随值列表的 ANY SOME 检索条件用等价的同等操作符和“ OR ”组成的表达式替换。
    例如,优化器将如下所示的第一条语句用第二条语句替换:
    sal > ANY (:first_sal, :second_sal)
    sal > :first_sal OR sal > :second_sal
    优化器将跟随子查询的 ANY SOME 检索条件转换成由“ EXISTS ”和一个相应的子查询组成的检索表达式。
    例如,优化器将如下所示的第一条语句用第二条语句替换:
    x > ANY (SELECT sal FROM emp WHERE job = 'ANALYST')
EXISTS (SELECT sal FROM emp WHERE job = 'ANALYST' AND x > sal)
2.7.14. ALL操作符优化:
    优化器将跟随值列表的 ALL 操作符用等价的“ = ”和“ AND ”组成的表达式替换。例如:
    sal > ALL (:first_sal, :second_sal) 表达式会被替换为:
    sal > :first_sal AND sal > :second_sal
    对于跟随子查询的 ALL 表达式,优化器用 ANY 和另外一个合适的比较符组成的表达式替换。例如
    x > ALL (SELECT sal FROM emp WHERE deptno = 10) 替换为:
    NOT (x <= ANY (SELECT sal FROM emp WHERE deptno = 10))
    接下来优化器会把第二个表达式适用 ANY 表达式的转换规则转换为下面的表达式:
    NOT EXISTS (SELECT sal FROM emp WHERE deptno = 10 AND x <= sal)
2.7.15. 全局参数2.8.其它因素2.8.1.减少访问数据库的次数
当执行每条 SQL 语句时, ORACLE 在内部执行了许多工作 :
解析 SQL 语句
估算索引的利用率
绑定变量
读数据块等等
由此可见 , 减少访问数据库的次数 , 就能实际上减少 ORACLE 的工作量。
2.8.2. 常使用COMMIT
只要有可能 , 在程序中尽量多使用 COMMIT, 这样程序的性能得到提高 , 需求也会因为 COMMIT 所释放的资源而减少。 COMMIT 所释放的资源 :
1 回滚段上用于恢复数据的信息 .
2 被程序语句获得的锁
3 redo log buffer 中的空间
4 ORACLE 为管理上述 3 种资源中的内部花费
2.8.3. 避免不必要的子查询
一个列的标签同时在主查询和 WHERE 子句中的查询中出现,那么很可能当主查询中的列值改变之后,子查询必须重新查询一次。查询嵌套层次越多,效率越低,因此应当尽量避免子查询。如果子查询不可避免,那么要在子查询中过滤掉尽可能多的行。
Select u.* from t_user u where u.areaid in (select a.id from t_area a where ja.name like ‘ 苏州 %’)
Select u.* from t_user u, (select a.* from t_area a where a.name like ‘ 苏州 %‘) a
  Where u.areaid = a.id
2.8.4. 合理的索引设计2.8.4.1.避免或简化排序:
应当简化或避免对大型表进行重复的排序。当能够利用索引自动以适当的次序产生输出时,优化器就避免了排序的步骤。以下是一些影响因素:
索引中不包括一个或几个待排序的列;
group by order by 子句中列的次序与索引的次序不一样;
排序的列来自不同的表。
为了避免不必要的排序,就要正确地增建索引,合理地合并数据库表(尽管有时可能影响表的规范化,但相对于效率的提高是值得的)。如果排序不可避免,那么应当试图简化它,如缩小排序的列的范围等。
2.8.4.2. 消除对大型表行数据的顺序存取
在嵌套查询中,对表的顺序存取对查询效率可能产生致命的影响。比如采用顺序存取策略,一个嵌套 3 层的查询,如果每层都查询 1000 行,那么这个查询就要查询 10 亿行数据。避免这种情况的主要方法就是对连接的列进行索引。例如,两个表:学生表(学号、姓名、年龄 ?? )和选课表(学号、课程号、成绩)。如果两个表要做连接,就要在“学号”这个连接字段上建立索引。
还可以使用并集来避免顺序存取。尽管在所有的检查列上都有索引,但某些形式的 WHERE 子句强迫优化器使用顺序存取。下面的查询将强迫对 orders 表执行顺序操作:
SELECT FROM orders WHERE (customer_num=104 AND order_num>1001) OR order_num=1008
虽然在 customer_num order_num 上建有索引,但是在上面的语句中优化器还是使用顺序存取路径扫描整个表。因为这个语句要检索的是分离的行的集合,所以应该改为如下语句:
SELECT FROM orders WHERE customer_num=104 AND order_num>1001
UNION
SELECT FROM orders WHERE order_num=1008
这样就能利用索引路径处理查询。
2.8.4.3. 聚集索引和非聚集索引
    非聚集索引和聚集索引最大的区别在于,他针对数据的索引列生成了一个已经进行了排序的索引页。而他本身的数据在物理存储上是没有顺序可言的。
也因为这样的结构的不同,所以聚集索引和非聚集索引有一个增加、修改、删除时候的效率比较。
2.8.4.3.1. 聚集索引使用注意事项
     定义聚集索引键时使用的列越少越好。
     • 包含大量非重复值的列。
    . • 使用下列运算符返回一个范围值的查询: BETWEEN > >= < <=
     •  被连续访问的列。
     •  回大型结果集的查询。
     • 经常被使用联接或 GROUP BY 子句的查询访问的列;一般来说,这些是外键列。对 ORDER BY GROUP BY 子句中指定的列进行索引,可以使 SQL Server 不必对数据进行排序,因为这些行已经排序。这样可以提高查询性能。
    •   OLTP 类型的应用程序,这些程序要求进行非常快速的单行查找(一般通过主键)。应在主键上创建聚集索引。
2.8.4.3.2. 聚集索引不适用于:
     • 频繁更改的列 。这将导致整行移动(因为 SQL Server 必须按物理顺序保留行中的数据值)。这一点要特别注意,因为在大数据量事务处理系统中数据是易失的。如果你插入的数据在已经有的数据的中间,那么他会引起一个分页这样的一个操作,这样会让你的操作更消耗一些。
     • 宽键 。来自聚集索引的键值由所有非聚集索引作为查找键使用,因此存储在每个非聚集索引的叶条目内。
非聚集索引:数据存储在一个地方,索引存储在另一个地方,索引带有指针指向数据的存储位置。
2.8.4.3.3. 聚集索引
      非聚集索引中的项目按索引键值的顺序存储,而表中的信息按另一种顺序存储(这可以由聚集索引规定)。对于非聚集索引,可以为在表非聚集索引中查找数据时常用的每个列创建一个非聚集索引。有些书籍包含多个索引。例如,一本介绍园艺的书可能会包含一个植物通俗名称索引,和一个植物学名索引,因为这是读者查找信息的两种最常用的方法。
2.8.4.3.4. 举例
例:表 card 7896 行,在 card_no 上有一个非聚集索引,表 account 191122 行,在 account_no 上有一个非聚集索引,试看在不同的表连接条件下,两个 SQL 的执行情况:
SELECT sum(a.amount) FROM account a,card b WHERE a.card_no = b.card_no
20 秒)
SQL 改为:
SELECT sum(a.amount) FROM account a,card b WHERE a.card_no = b.card_no and a.account_no=b.account_no
< 1 秒)
分析:
在第一个连接条件下,最佳查询方案是将 account 作外层表, card 作内层表,利用 card 上的索引,其 I/O 次数可由以下公式估算为:
外层表 account 上的 22541 + (外层表 account 191122 * 内层表 card 上对应外层表第一行所要查找的 3 页) =595907 I/O
在第二个连接条件下,最佳查询方案是将 card 作外层表, account 作内层表,利用 account 上的索引,其 I/O 次数可由以下公式估算为:
外层表 card 上的 1944 + (外层表 card 7896 * 内层表 account 上对应外层表每一行所要查找的 4 页) = 33528 I/O
可见,只有充份的连接条件,真正的最佳方案才会被执行。
多表操作在被实际执行前,查询优化器会根据连接条件,列出几组可能的连接方案并从中找出系统开销最小的最佳方案。连接条件要充份考虑带有索引的表、行数多的表;内外表的选择可由公式:外层表中的匹配行数 * 内层表中每一次查找的次数确定,乘积最小为最佳方案。
不可优化的 WHERE 子句
1
下列 SQL 条件语句中的列都建有恰当的索引,但执行速度却非常慢:
SELECT * FROM record WHERE substrINg(card_no,1,4)='5378'
(13 )
SELECT * FROM record WHERE amount/30< 1000
11 秒)
SELECT * FROM record WHERE convert(char(10),date,112)='19991201'
10 秒)
分析:
WHERE 子句中对列的任何操作结果都是在 SQL 运行时逐列计算得到的,因此它不得不进行表搜索,而没有使用该列上面的索引;如果这些结果在查询编译时就能得到,那么就可以被 SQL 优化器优化,使用索引,避免表搜索,因此将 SQL 重写成下面这样:
SELECT * FROM record WHERE card_no like '5378%'
< 1 秒)
SELECT * FROM record WHERE amount< 1000*30
< 1 秒)
SELECT * FROM record WHERE date= '1999/12/01'
< 1 秒)
2.8.4.4. 实例
例:表 record 620000 行,试看在不同的索引下,下面几个 SQL 的运行情况:
语句 A
SELECT count(*) FROM record
WHERE date >'19991201' and date < '19991214 and amount >2000
语句 B
SELECT count(*) FROM record
WHERE date >'19990901' and place IN ('BJ','SH')
语句 C
SELECT date,sum(amount) FROM record
group by date
1 date 上建有一个非聚集索引
A (25 )
B (27 )
C (55 )
分析:
date 上有大量的重复值,在非聚集索引下,数据在物理上随机存放在数据页上,在范围查找时,必须执行一次表扫描才能找到这一范围内的全部行。
2 date 上的一个聚集索引
A :( 14 秒)
B :( 14 秒)
C :( 28 秒)
分析:
在聚集索引下,数据在物理上按顺序在数据页上,重复值也排列在一起,因而在范围查找时,可以先找到这个范围的起末点,且只在这个范围内扫描数据页,避免了大范围扫描,提高了查询速度。
3 place date amount 上的组合索引
A :( 26 秒)
C :( 27 秒)
B :( < 1 秒)
分析:
这是一个不很合理的组合索引,因为它的前导列是 place ,第一和第二条 SQL 没有引用 place ,因此也没有利用上索引;第三个 SQL 使用了 place ,且引用的所有列都包含在组合索引中,形成了索引覆盖,所以它的速度是非常快的。
4 date place amount 上的组合索引
A (< 1 )
B :( < 1 秒)
C :( 11 秒)
分析:
这是一个合理的组合索引。它将 date 作为前导列,使每个 SQL 都可以利用索引,并且在第一和第三个 SQL 中形成了索引覆盖,因而性能达到了最优。
总结 1
缺省情况下建立的索引是非聚集索引,但有时它并不是最佳的;合理的索引设计要建立在对各种查询的分析和预测上。一般来说:
有大量重复值、且经常有范围查询( between, >,< >=,< = )和 order by group by 发生的列,考虑建立聚集索引;
经常同时存取多列,且每列都含有重复值可考虑建立组合索引;在条件表达式中经常用到的不同值较多的列上建立检索,在不同值少的列上不要建立索引。比如在雇员表的“性别”列上只有“男”与“女”两个不同值,因此就无必要建立索引。如果建立索引不但不会提高查询效率,反而会严重降低更新速度。
组合索引要尽量使关键查询形成索引覆盖,其前导列一定是使用最频繁的列。
2.8.5. 避免使用不兼容的数据类型:
    例如float和int、char和varchar、binary和varbinary是不兼容的。数据类型的不兼容可能使优化器无法执行一些本来可以进行的优化操作。例如:
SELECT name FROM employee WHERE salary > 60000
在这条语句中,如salary字段是money型的,则优化器很难对其进行优化,因为60000是个整型数。我们应当在编程时将整型转化成为钱币型,而不要等到运行时转化。

2.8.6. 存储过程或Dao层中,采用临时表优化查询:


1.从parven表中按vendor_num的次序读数据:
SELECT part_num,vendor_num,price FROM parven ORDER BY vendor_num
INTO temp pv_by_vn
这个语句顺序读parven(50页),写一个临时表(50页),并排序。假定排序的开销为200页,总共是300页。
2.把临时表和vendor表连接,把结果输出到一个临时表,并按part_num排序:
SELECT pv_by_vn,* vendor.vendor_num FROM pv_by_vn,vendor
WHERE pv_by_vn.vendor_num=vendor.vendor_num
ORDER BY pv_by_vn.part_num
INTO TMP pvvn_by_pn
DROP TABLE pv_by_vn
这个查询读取pv_by_vn(50页),它通过索引存取vendor表1.5万次,但由于按vendor_num次序排列,实际上只是通过索引顺序地读 vendor表(40+2=42页),输出的表每页约95行,共160页。写并存取这些页引发5*160=800次的读写,索引共读写892页。
3.把输出和part连接得到最后的结果:
SELECT pvvn_by_pn.*,part.part_desc FROM pvvn_by_pn,part
WHERE pvvn_by_pn.part_num=part.part_num
DROP TABLE pvvn_by_pn
这样,查询顺序地读pvvn_by_pn(160页),通过索引读part表1.5万次,由于建有索引,所以实际上进行1772次磁盘读写,优化比例为30∶1。

2.9. 常见优化工具介绍

2.9.1. 用Explain Plan分析SQL语句
EXPLAIN PLAN 是一个很好的分析SQL语句的工具, 它甚至可以在不执行SQL的情况下分析语句。通过分析, 我们就可以知道ORACLE是怎么样连接表,使用什么方式扫描表(索引扫描或全表扫描)以及使用到的索引名称。
(2)  SQL PLUS的TRACE

3. 常用术语

u 全表扫描
全表扫描就是顺序地访问表中每条记录,ORACLE采用一次读入多个数据块(database block)的方式优化全表扫描
u 通过索引访问(ROWID )
ROWID包含了表中记录的物理位置信息,ORACLE采用索引实现了数据和存放数据的物理位置(ROWID)之间的联系,通常索引提供了快速访问ROWID的方法,因此那些基于索引列的查询就可以得到性能上的提高。
u 共享SQL
(1)Oracle提供对执行过的SQL语句进行高速缓冲的机制。被解析过并且确定了执行路径的SQL语句存放在SGA的共享池中。
(2)Oracle执行一个SQL语句之前每次先从SGA共享池中查找是否有缓冲的SQL语句,如果有则直接执行该SQL语句。
(3)可以通过适当调整SGA共享池大小来达到提高Oracle执行性能的目的。
u ORACLE优化器
(1)RULE(基于规则)
(2)COST(基于成本)
(3)CHOOSE(选择性)


 
         关注微信公众号每天学习一点程序员的职业经



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值