还记得我们一起优化的SQL吗?

SQL如何进行优化,看这篇就好了 在应用开发的早期,数据量少,开发人员开发功能时更重视功能上的实现,随着生产数据的增长,很多 SQL语句开始暴露出性能问题,对生产的影响也越来越大,有时可能这些有问题的 SQL 就是整个系统性能的瓶颈。... 阅读详情

一、背景

随着业务不断迭代,系统中出现了较多的SQL慢查。慢查虽不致命,但会让商家感知到系统较慢,影响使用体验。在进行慢查优化过程中,我们积累了一些经验。本文将基于我们的实战经历,讲解工作中比较常见的慢查原因,以及如何去优化。

本文讲解基于MySQL 5.7。

二、慢查优化

本节主要针对常见的慢查进行分类,讲解怎么去优化。

2.1 建立索引的 正确姿势

数据量较大的时候,如果没有索引,那么数据库只能全表一行一行的遍历判断数据,因此优化SQL的时候,第一步要做的就是确定有没有合适的可用的索引。在业务本身比较复杂的情况下,一个表会涉及各种各样的查询方式,因此我们需要建立各种各样的索引去提高查询。然而索引数量过多又会影响增删改的效率,并且也会占用更多额外的空间去存储索引,因此我们要懂得怎么去正确地建立索引,避免索引滥用。

2.1.1 索引不要包含选择性过低字段

选择性过低,即通过该字段只能过滤掉少部分的数据,是没必要建立索引的,因为如果该数据只是占小部分,即使没有索引直接查询数据表也不用过多的遍历即可找到目标数据,没有必要基于索引查询。

SQL:

select * from my_table where col_a=1 and col_b=1

索引:
index (col_a,col_b)

col_b为逻辑删除字段,包含0未删除和1已删除,数据库中的值只有很少量部分是逻辑删除的。但是在业务中我们一般都只查未删除的,那么这种情况col_b是完全不必要在索引中的,可以把col_b从组合索引中去掉。

2.1.2 选择性高的字段前置或者单独建立索引

SQL:

select * from my_table where col_a=1 and col_b=1 and col_c=1

索引:
index(col_a,col_b,col_c)

假设选择性col_c>col_b>col_a,抛开业务本身需要,组合索引建立的顺序尽可能建为index(col_c,col_b,col_a)。

原因是组合索引底层的存储先按照第一个进行排序,第一个字段相同再按照第二字段排序,如果选择性低的字段放在前面,因此选择性高的字段放前面相对而言IO的次数可能会减少一些。比如基于第一个字段过滤完会有10000条数据,基于第二个字段过滤完只有100条数据,如果先查第一个字段则需要在10000条数据的基础上再进行过滤查询,而基于第二字段过滤完只需要基于100条数据进行过滤查询即可。

而如果col_c选择性特别高,经过col_c过滤后只剩下极少的数据甚至一条数据,单独建立col_c索引就可以。

需要注意的是这个规则特别提到了抛开业务本身需要,比如如果col_a选择性比col_b高一点相差不大,但是col_b在业务场景中通用性更强,那么col_b放在前面更合适,可以减少创建的索引。

2.1.3 尽量使用覆盖索引

SQL:

SELECT sum(col_c) FROM my_table where col_a=1 and col_b=1

索引:
index(col_a,col_b)

如果col_a和col_b过滤完后还有大量数据,那么建议建一个 index(col_a,col_b , col_c) 索引,否则MySQL需要通过大量回表去查询col_c的数据再去求和。

2.1.4 小结

1、选择性低的字段不用建立索引。

2、具有唯一性或者高选择性的字段无需与其他字段建立组合索引。

3、除了业务需求上的考虑,尽量将选择性高地索引字段前置。

4、在经过索引过滤后数据量依旧很大的情况下可以考虑通过覆盖索引优化。

2.2 使用索引的正确姿势

除了SQL本身没有适用的索引,有了相关的索引但是对应的索引没有生效是比较常见的情况,以下列举一些常见的失效场景,在日常的开发中,我们要尽量避免。

需要注意的是,索引失效这里指的是没有利用到索引的二分查询进行数据过滤。因为存在ICP,所以会存在触发了失效场景执行计划还是显示使用了索引的情况。

2.2.1 最左匹配截断

SQL:

select * from my_table where col_b=1  select * from my_table order by col_b

索引:
index(col_a,col_b)

组合索引的匹配规则是从左往右匹配,无论是作为过滤条件还是排序条件都要遵循这个原则。如果要使用col_b字段走索引,查询条件则必须要携带col_a字段。

补充说明:
1、col_b作为排序字段如果要走索引,只要保证组合索引中col_b前面的字段都可以包含在过滤条件或者排序条件中即可,也不需要保证col_b作为组合索引中的最后一个字段。

比如:

select * from my_table order by col_a,col_b

col_a和col_b都可以走索引。

2、如果col_b是作为过滤条件,则col_b前面的字段都应该在过滤条件中。
比如:

select * from my_table where col_b=1 order by col_a

col_a和col_b都走不了索引,因为col_a在组合索引左边,但是col_a不在查询条件中。

2.2.2 隐式转换

字段类型:
col_a(VARCHAR)
col_b(DATETIME)

索引:
index1(col_a)
index2(col_b)

SQL:

select * from my_table where col_a=1  select * from my_table where col_b=1603296000000

失效原因
字段类型和查询数据的值类型不一致,会导致字段上的索引失效。

  • col_a是字符类型,使用了数字类型进行查询。
  • col_b是datetime类型,针对datetime/date/time类型,MySQL增删查改都要基于字符串形式日期去处理,否则MySQL就需要额外进行转换。(虽然底层储存的是数字类型,但是并不是存储时间戳,底层是处理是统一将外部传入的字符串进行转换,比如是date类型通过将 “2021-12-01” 字符串转数字 20211201 这种形式去存储)。

2.2.3 in + order by 导致排序失 效

索引:
index(col_a,col_b)

SQL:

select * from my_table where col_a in (1,2) order by col_b

解决方式:

  • 如果col_a的过滤性不高,在组合索引中可以通过将col_b字段前置,将col_a移动到组合索引后面,只用于避免或减少回表。
  • 如果col_a的过滤性高,过滤后的数据相对较少,则维持当前的索引即可,剩余不多的数据通过filesort进行排序。
  • 如果存在大量数据,并且经过col_b过滤后还是存在大量数据,建议基于别的数据存储实现,比如Elasticsearch。

另外SQL建议调整为只查询id(或者其他已经在索引中的字段),再根据id去查对应的数据。可以促使SQL走覆盖索引进一步优化、也可以促使MySQL底层在进行filesort使用更优的排序算法。

2.2.4 范围查询阻断组合索引

索引:
index(col_a,col_b)

SQL:

select * from table where col_a >'2021-12-01' and col_b=10

解决方式:
可以调整下索引顺序,col_a放在最后面。index(col_b,col_a)

2.2.5 后缀匹配不能走索引

索引:
index(col_a,col_b)

SQL:

select * from table where col_a=1 and col_b like '%name%'

以上SQL会导致索引失效。前缀匹配比如name%是可以走索引的,但是后缀匹配比如%name会导致没办法基于索引树进行二分查找。如果需要进行后缀匹配,数据量较大建议基于Elasticsearch实现。

2.2.6 or查询导致失效

索引:
index(col_a,col_b)

SQL:

select * from table where col_a=1 or col_b=''

or查询会导致索引失 效,可以将col_a和col_b分别建 立索引,利用Mysql的index merge(索引合并)进行优化。 本质上是分别两个字段分别走各自索引查出对应的数据,再将数据进行合并。

2.2.7 使用函数查询或运算

索引:
index(col_a,col_b)

SQL:

select * from table where col_a=1 and DATE_SUB(CURDATE(), INTERVAL 7 DAY) <= date(col_b);  select * from table where col_a=1 and col_b+1=10

2.2.8 不等于、不包含(只用到ICP)

索引:
index(col_a,col_b,col_c)

SQL:

select * from table where col_a=1 and col_b not in (1,2)  select * from table where col_a=1 and col_b != 1

2.2.9 选择性过低,直接走全表

选择性过低会导致索引失效。由于通过二级索引查询后还有回表查询的开销,如果通过该字段只能过滤少量的数据,整体上还不如直接查询数据表的性能,则MySQL会放弃这个索引,直接使用全表扫描。底层会根据表大小、IO块大小、行数等信息进行评估决定。

索引:

index(col_a)

SQL:

select * from table where col_a>'2017-10-22'

2.2.10 asc和desc混用

索引:
index(col_a,col_b,col_c)

SQL:

select * from my_table where col_a=1 order by col_b desc,col_c asc

desc 和asc混用时会导致索引失效,不建议混用。

2.2.11 小结

根据以上例子,总结几个索引失效的场景:

  1. 组合索引左匹配原则
  2. 发生隐式转换
  3. 组合索引,in + order by in会阻断排序用索引
  4. 范围查询会阻断组合索引,索引涉及到范围查询的索引字段要放在组合索引的最后面。
  5. 前模糊匹配导致索引失效
  6. or查询,查询条件部分有索引,部分无索引,导致索引失效。
  7. 查询条件使用了函数运算、四则运算等。
  8. 使用了!=、not in
  9. 选择性过低
  10. asc和desc混用

2.3 编写SQL的正确姿势

懂得怎么建立索引,也懂得了怎么避免索引失效,但是有些场景即使索引走对了,也会慢查,这时我们要考虑优化我们SQL写法。

2.3.1 深分页

索引:

index(col_c)

SQL:

select * from my_table where col_c=1 limit 1000,10

为什么深分页会效率变差,首先我们要了解一下分页的原理。

MySQL limit不会传递到引擎层,只是在服务层进行数据过滤。查询数据时,先由引擎层通过索引过滤出一批数据(索引过滤),然后服务层进行二次过滤(非索引过滤)。

引擎层过滤后会将获取的数据暂存,服务层一条一条数据获取,获取时引擎层回表获得完成数据交给服务层,服务层判断是否匹配查询条件(非索引过滤),如果匹配会继续判断是否满足limit限制的数据范围,符合并且范围内的数据都查完了才返回。

所以如果深分页,会导致大量的无效回表(前1000条进行了回表,实际上只需要1000-1010的数据),因此优化的方式就是避免深分页带来的额外回表。

解决方式:

# 避免深分页select * from my_table where id>上次查询的数据id值 limit 100# 延迟关联 避免大量回表SELECT * FROM my_table t1,(select id from my_table where col_c=1 limit 1000,100) t2 where t1.id=t2.id

避免深分页: 我们可以改成id过滤,每次都只查询大于上次查询的数据id。这样每次只查询100条,回表也只需要回表100条。

覆盖索引: 如果业务需要的字段比较少,可以通过保证SQL查询的字段和查询条件都在索引上,避免回表。

延迟关联: 通过延迟关联,通过编写完全基于索引查询数据的SQL,再根据id查询详细的字段数据。

2.3.2 order by id

索引:
index(col_a)

SQL:

select * from table where col_a=1 and col_b=2 order by id

MySQL INNODB二级索引最终叶子结点引用的都是主键id,因此我们可以利用这个点去使用id排序。

但是在本场景中,col_b截断了索引,导致SQL没法利用id进行索引排序。而主键索引的权重会比较高,可能会导致MySQL没有正确选择索引,从而选择了可能性能更差的主键索引去做排序,查询条件通过遍历扫描数据。

因此在不能保证id排序可以走索引的情况下,建议改用其他字段排序。如果查询结果集确定会比较少排序字段可以不在索引上,如果结果集较大还是要把排序字段加到索引中。

三、慢查分析

在掌握了SQL优化的理论知识之后,我们怎么验证编写的SQL是否有按照预期使用了比较合适的索引?这就需要学会分析SQL的执行情况。

执行计划:我们可以通过explain关键字查看SQL的执行计划,通过执行计划分析SQL的执行情况。

执行计划字段描述:

extra字段常见值:

using index condition补充说明:
using index condition表示使用了ICP(Index Condition Pushdown索引下推),ICP是MySQL 5.6之后新增的特性,部分字段因为某些情况无法走索引查询,则会展示using where(在server层进行数据过滤),ICP是在存储引擎层进行数据过滤,而不是在服务层过滤,利用索引现有的数据过滤调一部分数据。

using where 和 using index condition的区别:

为什么需要ICP机制:

索引:

index(col_a,col_b)

SQL:

select * from my_table where col_a="value" and col_b like "%value%"

如果没有using index condtion,col_a会走索引查询,匹配到对应的数据后,回表查出剩余字段信息,再去匹配col_b。假设col_a过滤后还有10000条数据,而通过col_b过滤后只会剩余1000条数据,则有9000条的数据是没必要的回表。

本质上索引树上是包含col_b字段的,只是col_b不能利用索引树二分查找特性(因为使用了前模糊匹配),但是可以利用索引上现有的数据进行遍历,减少无效回表。有了ICP后,基于索引就可以过滤col_a和col_b字段,过滤后只会剩下1000条数据,减少了大量的回表操作。

小结:
通过执行计划我们可以分析出SQL最终使用了什么索引,对索引的使用是处于什么情况,进而可以得出还有没有优化空间。

四、总结

我们要有质量意识,做好预防而不是做补救,SQL优化在开发阶段就要考虑清楚,而不是等上线后出现慢查了才去优化。

做好SQL优化可以记住一个口诀,有用高。SQL要有索引(建立正确的索引),索引要可用(避免索引失效),最后要考虑高效(覆盖索引、索引的选择性)。

Zynq UltraScale+ RFSoC实战:用XCZU49DR开发板实现雷达信号处理全流程 本文详细介绍了如何利用Xilinx Zynq UltraScale+ RFSoC系列中的XCZU49DR开发板,高效实现雷达信号处理全流程。文章深入剖析了该芯片集成的射频直采ADC/DAC、可编程逻辑与Arm多核处理器的异构计算优势,并通过实战案例展示了从环境搭建、硬件链路设计到软件优化的完整开发路径,为相控阵雷达等高性能应用提供了单芯片解决方案。 阅读详情

相关推荐

解读区块链在制药和物流管理中具备的优势

供应链透明度是建立药品弹性供应链的关键,如何更好地促进端到端的透明度,促进合作伙伴和消费者之间的信任度是当前医药和物流行业高管们关注的话题,区块链技术可以很好的帮助他们。 链集市 · 让区块链落地更简单 《区块链行业观察》专栏·第 31 篇 作者丨Shams Rahman 图片丨来源于网络 作者介绍:本文作者为Shams Rahman教授,是RMIT大学商业IT和物流学院的供应链管理教授、研究与创新部门副主任。本文中文版本由链集市团队编译整理,英文版权归原作者所有,中文转载请联系编译。 新冠肺炎疫情的爆发

CECBC的博客 626

MySQL-SQL优化

前言 在应用开发的早期,数据量少,开发人员开发功能时更重视功能上的实现,随着生产数据的增长,很多SQL语句开始暴露出性能问题,对生产的影响也越来越大,有时可能这些有问题的SQL就是整个系统性能的瓶颈。 SQL优化一般步骤 1、通过慢查日志等定位那些执行效率较低的SQL语句 2、explain 分析SQL的执行计划 需要重点关注type、rows、filtered、extra。 type由上至下,效率越来越高 ALL 全表扫描 index 索引全扫描 range 索引范围扫描,常用语&lt.

m0_50180963的博客 1437

Arduino for ESP32-----ESP-NOW介绍及使用

ESP-NOWESP-NOW介绍ESP-NOW支持以下特性ESP-NOW技术也存在以下局限性获取ESP32的MAC地址ESP-NOW单向通信(One-way communication)ESP32单板间的双向通信一对多通信(一发多收)一对多通信(多发一收) ESP-NOW介绍 ESP-NOW是一种由Espressif开发的协议,可以让多个设备在不使用Wi-Fi的情况下相互通信。该协议类似于低功耗的2.4GHz无线连接。设备之间的配对需要在通信之前完成。配对完成后,连接是安全的、点对点的,不需要握手。这意味着

Better_man1的博客 2万+

对sql优化理解和如何sql优化?

SQL优化是数据库管理和性能调优的重要方面,它涉及对SQL查询语句和数据库结构的调整,以提高查询效率、减少响应时间、降低系统资源消耗。

H___Hkk的博客 1659

【现代信号处理】01 -概率论与随机过程回顾

概率论与随机过程回顾 文章目录概率论与随机过程回顾1. 统计模型的相关概念1.1 统计实验(不确定性)1.2 样本点1.3 样本空间Ω1.4 概率1.5.统计模型是先验的2. 概率和统计的差异2.1 联系2.2 差异2.3.1 概率和统计问题的三要素2.3.1.1 model-模型2.3.1.2 data-数据2.3.1.3 decision-决策2.3.2 关系图3. 样本空间的重要性3.1 贝朗特悖论3.1.1 解法一3.1.2 解法二3.1.3 解法三3.2 结论4.概率相关的概念4.1 随机变量4.

qq_41741344的博客 1406

U盘 未知USB设备 设定地址失败 由于该设备有问题Windows 已将其停止(代码 43) 终极解决方案(做过系统装机盘而无法解决的必看)

U盘由于该设备有问题Windows 已将其停止(代码 43) 终极解决方案 我们在使用U盘的时候偶尔会碰到下列情况 一般是因为传输数据的过程中,死机或未响应直接断点或拔掉设备导致的,U盘再次插上之后出现设定地址失败。无法再次读取设备的数据。 解决方案: 首先请确认出现该情况不是因为你摔了U盘或接口处产生断裂这种物理损伤导致的!!! 首先请确认出现该情况不是因为你摔了U盘或接口处产生断裂这种物理损...

qq_39123987的博客 13万+

SQL查询优化

SQL查询优化 一、获取有性能问题SQL的三种方法 通过用户反馈获取存在性能问题的SQL 通过慢查询日志获取存在性能问题的SQL 实时获取存在性能问题的SQL 二、慢查询日志介绍 1、使用慢查询日志获取有性能问题的SQL (1) 参数配置 ...

weixin_38477351的博客 2万+

公交车调度排班优化算法研究与实现

本文提出的公交车调度排班优化算法基于遗传算法,能够较好地处理公交车调度排班问题,使得车辆数量和运营时间得到平衡,公交车的使用效率得到了优化。在模拟的公交车路线中,使用本文所提出的算法,得到了一组较优的公交车调度排班方案。其中,车辆数量的多少会影响到公交车的行驶速度和等候时间,而运营时间的长短则会直接影响到公交车的使用效率和成本。本文基于遗传算法,通过随机生成种群、选择、交叉、变异等操作,逐步优化种群中个体的适应度,最终得到较优的公交车调度排班方案。以下是本文中所使用的Matlab代码实现,供参考。

PixelLoom的博客 872

SQL优化

索引调优和SQL语句调优

FH201611的博客 1519

用业务语义织就企业数据之网:SAP Datasphere 的定位、架构与落地场景全解析

SAP Datasphere作为SAP业务数据云的核心组件,通过语义层整合企业内外部数据,支持实时分析与AI应用。其核心功能包括数据集成、语义建模、虚拟化与仓库能力,通过Spaces实现资源隔离,Data Builder和Business Builder分别面向技术和业务建模。该平台与SAP HANA Cloud、SAC等深度集成,支持财务统一分析、供应链可视化等典型场景,并可通过BW bridge实现传统BW资产云化迁移。Datasphere强调开放互联,能与Databricks等生态伙伴协同,为企业提供

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 1366

详解Linux下使用vscode编译运行和调试C/C++

本教程将具体详解使用Visual Studio Code在Linux环境下使用C/C++方法。 GCC C++ 编译器 (g++) 和 GDB 调试器的方法。 GCC是GNU 编译器集合; GDB 是 GNU 调试器。 1.先决条件 安装Visual Studio Code. 安装C++拓展插件。点击在vscode界面最左侧的Extensions图标(打开快捷键:ctrl+shift+X),搜索“C/C++”,点击进行安装。 确保已安装gcc. 一般ubuntu系统会预装gcc.在终端窗口中输入如下命令

休的博客 6242

Sql(sql语句优化,索引设计优化)

Sql(sql语句优化,索引设计优化)

轻言细语莫泪痕 1954

总结个人关于雷达样式的见解(脉内脉间调制)

概述 雷达有各种各样的脉内脉间调制样式,对于雷达的设计者来说,不外乎三个目的: 1、增加雷达性能,如提高测距精度,增大侦察距离等; 2、雷达的工作机制决定雷达必须采用此种样式的调制,如有些SAR雷达为保证径向测距精度,需要使用脉内宽带调制信号; 3、降低雷达被电子战装备截获的概率,也就是LPI设计。 对于雷达对抗方,则只会观察到信号存在脉内及脉间的调制。对于一般的雷达信号,通常只采用脉内调制,脉间频率调制,脉间PRI调制。 脉间调制类型 1、PD雷达的脉间特征 雷达进行脉间调制的主要是PRI..

年糕的博客 3万+

[MySQL]SQL优化之sql语句优化

SQL查询优化在MySQL数据库中是提高性能的关键步骤之一。本文总结了一系列针对SQL查询的优化技巧,以帮助开发者改善查询性能,降低系统负载。

Panci_的博客 1491

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

SQL - SQL优化

在中为了提高查询效率,我们常常会采取一些措施对查询语句进行sql优化,下面总结的一些方法,有需要的可以参考参考。

MinggeQingchun的博客 1689

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

MySQL——SQL优化

SQL优化是提升数据库性能的关键。本文总结了17个SQL优化技巧,包括避免SELECT*、使用UNION ALL代替UNION、小表驱动大表、批量操作、合理使用LIMIT等查询优化方法。同时介绍了索引优化原则、数据库结构优化策略,以及如何通过执行计划分析、分页优化、配置调整等提高性能。文章还强调了避免大事务、使用存储过程等建议,并指出SQL优化需要结合具体业务场景持续进行。通过遵循这些优化策略,可以显著提升查询效率和数据库整体性能。

hj945218的博客 1055

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

MySQL优化之SQL优化详解

sql优化详解

个人博客,欢迎收藏。 1591

sql 不等于优化_Oracle SQL优化

一 > 应用ORACLE的HINT(提示)处理:提示处理是在ORACLE产生的SQL分析执行路径不满意的情况下要用到的。它可以对SQL进行以下方面的提示 1. 目标方面的提示: COST(按成本优化) RULE(按规则优化) CHOOSE(缺省)(ORACLE自动选择成本或规则进行优化) ALL_ROWS(所有的行尽快返回) FIRST_RO...

weixin_39997253的博客 5331

SQL语句优化

一、找出导致性能慢的原因       首先你要知道是否跟sql语句有关,然后使用sql性能检测工具--sql server profiler,分析出sql慢的相关语句,就是执行时间过长,占用系统资源,cpu过多的 1.查看执行时间和cpu占用时间(查询后在消息中查看) set statistics time on select * from dbo.Product set stat...

爱笑的喵 7796

达梦SQL 优化简介

简单来说,执行计划就是一条 SQL 语句在数据库中的执行过程或访问路径的描述。SQL 语言是种功能强大且非过程性的编程语言,比如以下这条 SQL 语句:开发人员只关心 SQL 语句能否返回 T1 与 T2 表的关联查询结果,不需要指定该 SQL 如何执行,也就是说不关心该 SQL 是先访问 T1 表还是先访问 T2 表。对于 SQL 来说,两种访问方式就是两个执行计划,查询优化器 (CBO) 将根据代价也就是开销来选择最优的执行计划。以如下 SQL 语句执行计划为例:

weixin_48536597的博客 2792
上一篇: 重新刷新你对Redis集群的理解
下一篇: Redis Pipeline还可以这么用的
Java_ttcd
博客等级 码龄4年 198粉丝 190原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值