构建高性能WebSphere企业级应用-第9章读书笔记-死锁问题实例分析

一、抽象死锁的原理

1。互斥锁:又称X锁。当事务对数据加X锁,则其它事务都不能再对该数据加任何类型的锁,也就是说X锁不能与其他任何类型的锁共存,直到该事务释放X锁。一般在修改数据前要给数据加互斥锁,所以,互斥锁又称为写锁。

2。共享锁:又称S锁。当事务对数据加S锁,则其它事务只能加S锁,则其它事务只能加S锁,不能加X锁,一般在读取数据前要加共享锁,所以共享锁又称读锁。

二、Db2的锁提供的属性

1。object:该属性标识要锁定的数据资源。DB2数据库管理程序在需要时锁定数据资源(如表空间、表和行)。DB2支持对表空间、表、行和索引加锁(大型机上的DB2还支持对数据页加锁)来保证数据库的并发完整性。不过,在考虑用户应用程序的并发性的问题上,通常并不检查用于表空间和索引的锁。分析该类问题的焦点在于表锁和行锁。

2。size:该属性指定要锁定的数据资源部分的物理大小。锁并不总是必须控制整个数据资源。例如,DB2数据库管理程序可以让应用程序独占地控制表中的特定行,而不是让该应用程序独占地控制整个表。

3。duration:该属性指定持有锁的时间长度。事务的隔离级别通常控制着锁的持续时间。

4。mode:该属性指定允许锁的拥有者执行的访问类型,以及允许并发用户对被锁定数据资源执行的访问类型。这个属性通常称为锁状态。


三、锁的相容性

1。参考 循序渐进DB2-系统管理、运行维护与应用案例 http://book.51cto.com/art/200906/129276.htm

 上图是锁的相容关系矩阵


四、锁的状态

DB2在表一级加锁可以使用表10-3所示的方式。

表10-3  表一级的加锁方式

名称缩写

   

   

IN

无意图(Intenet None)锁

不需要行锁

该锁的拥有者可以读表中的任何数据,

包括其他事务尚未提交的数据,但不能对

表中的数据进行更改

IS

意图共享(Intent Share)锁

需要行锁配合

该锁的拥有者在拥有相应行上的S锁时

可以读取该行的数据,但不能对表中的

数据进行更改

IX

意图排它(Intent eXclusive)

锁需要行锁配合

该锁的拥有者在拥有相应行的X锁时可

以更改该行的数据

SIX

共享携意图排它

(Share with Intent
eXclusive)锁

需要行锁配合

该锁的拥有者可以读表中的任何数据,如果

在相应的行上能够获得X锁,则可以修改该行。

SIX锁的获得比较特殊,它是在应用程序已经拥有

IX锁的情况下请求S锁,或者是在应用程序已经

拥有S锁的情况下请求IX锁时生成的

S

共享(Share)锁

不需要行锁配合

该锁的拥有者可以读表中的任何数据,

如果表被加上S锁,该表中的数据就

只能被读取,不能被改变

U

更新(Update)锁

不需要行锁配合

该锁的拥有者可以读表中的任何数据,

在升级到X锁之后,还可以更改表中的任

何数据。该锁是处于等待对数据进行

更改的一种中间状态

X

排它(eXclusive)锁

不需要行锁配合

该锁的拥有者可以读取或更改表中的

任何数据。如果对表加上X锁,除了未

提交读程序外,其他应用程序

都不能对该表进行存取

Z

超级排他
(Super Exclusive)

不需要行锁配合

该锁不是通过应用程序中的DML语言来

生成的。一般是通过对表进行删除(Drop)

和转换(Alter)操作或创建和删除索引

而获得的。如果对表加上Z锁,

其他应用程序(包括未提交读程序)

都不能对该表进行存取


五、锁的策略

DB2可以只对表进行加锁,也可以对表和表中的行进行加锁。如果只对表进行加锁,则表中所有的行都受到同等程度的影响。如果加锁的范围针对表及下属的行,则在对表加锁后,相应的数据行上还要加锁。应用程序究竟是对表加行锁还是同时加表锁和行锁,是由应用程序执行的命令和系统的隔离级别确定的。如果某个应用程序挂起某个数据库对象上的锁定,那么另一个应用程序就可能不能访问该对象。因此,锁定最少量数据并使这些数据不可访问的行级别锁定相比表级别而言,对于最大化并行性更好。但是,锁定需要内存和处理时间,因此单个表锁定可以最小化锁定开销。


六、隔离级别

护数据库的一致性和数据完整性,同时又允许多个应用程序同时访问同一数据,这样的特性称为并发性。DB2数据库用来尝试强制实施并发性的方法之一是通过使用隔离级别,它决定在第一个事务访问数据时,如何对其他事务锁定或隔离该事务所使用的数据。DB2使用下列隔离级别来强制实施并发性:

可重复读(Repeatable Read)

读稳定性(Read Stability)

游标稳定性(Cursor Stability)

未提交读(Uncommitted Read)

可重复读隔离级别可以防止所有现象,但是会大大降低并发性(可以同时访问同一资源的事务数量)。未提交读隔离级别提供了最大的并发性,但是"脏读"、"幻像读"和"不可重复读"都可能出现。默认的隔离级别是CS。

1。  可重复读(RR-Repeatable Read)

可重复读隔离级别是最严格的隔离级别。在该隔离级别下,一个事务的影响完全与其他并发事务隔离,脏读、不可重复的读、幻像读现象都不会发生。当使用可重复读隔离级别时,在事务执行期间会锁定该事务以任何方式引用的所有行。因此,如果在同一个事务中发出同一个SELECT语句两次或更多次,那么产生的结果数据集总是相同的。因此,使用可重复读隔离级别的事务可以多次检索同一行集,并对它们执行任意操作,直到提交或回滚操作终止该事务。但是,在事务存在期间,不允许其他事务执行会影响这个事务正在访问的任何行的插入、更新或删除操作。为了确保这种行为不会发生,锁定该事务所引用的每一行-- 而不是仅锁定被实际检索或修改的那些行。因此,如果一个事务扫描了1000行,但只检索10行,那么它所扫描的1000行(而不仅是被检索的10行)都会被锁定。

那么在现实环境中可重复读隔离级别是如何工作的呢?假定如家酒店使用DB2数据库跟踪如家酒店的客房信息,包括房间预订和房价信息,还有一个基于Web的应用程序,它允许顾客按"先到先服务"的原则预订房间。如果旅馆预订应用程序是在可重复读隔离级别下运行的,当顾客扫描某个日期段内的可用房间列表时,您(旅馆经理)将无法更改那些房间在指定日期范围内的房价。同样,其他顾客也无法进行或取消将会更改该列表的预订(直到第一个顾客的事务终止为止)。图10-6说明了这种行为。

 
(点击查看大图)图10-6  可重复读隔离级别的示例

在使用可重复读隔离级别时,一个事务中所有被读取过的行上都会被加上S锁,直到该事务被提交或回滚,行上的锁才会被释放。这样可以保证在一个事务中即使多次读取同一行,得到的值也不会改变。另外,在同一事务中即使以同样的搜索标准重新打开已被处理过的游标,得到的结果集也不会改变。可重复读相对于读稳定性而言,加锁的范围更大。对于读稳定性,应用程序只对符合要求的所有行加锁;而对于可重复读,应用程序将对所有被扫描过的行都加锁。

可重复读(RR)会锁定应用程序在工作单元中引用的所有行。利用"可重复读",在打开游标的相同工作单元内一个应用程序发出一个SELECT语句两次,每次都返回相同的结果。利用"可重复读",不可能出现丢失更新、脏读和幻像读的情况。


在该工作单元完成之前,"可重复读"应用程序可以尽可能多次地检索和操作这些行。但是,在该工作单元完成之前其他应用程序均不能更新、删除或插入可能会影响结果表的行。"可重复读"应用程序不能查看其他应用程序的未落实更改。

2。读稳定性(RS-Read Stability)

读稳定性隔离级别没有可重复读隔离级别那么严格;因此,它没有将事务与其他并发事务的效果完全隔离。读稳定性隔离级别可以防止脏读和不可重复读,但是可能出现幻像读。在使用这个隔离级别时,只是锁定事务实际检索和修改的行。因此,如果一个事务扫描了1000行,但只检索10行,则只有被检索的10行(而不是所扫描的1000行)被锁定。因此,如果在同一个事务中发出同一个SELECT语句两次或更多次,那么每次产生的结果数据集可能不同。

与可重复读隔离级别一样,在读稳定性隔离级别下运行的事务可以检索一个行集,并可以对它们执行任意操作,直到事务终止。在这个事务存在期间,其他事务不能执行那些会影响这个事务检索到的行集的更新或删除操作;但是其他事务可以执行插入操作。如果插入的行与第一个事务的查询的选择条件匹配,那么这些行可能作为幻像出现在后续产生的结果数据集中。其他事务对其他行所作的更改,在提交之前是不可见的。

那么,读稳定性隔离级别会如何影响如家酒店客房预定应用程序的工作方式呢?当一个顾客检索某个日期段内的所有可用房间列表时,您可以更改这个顾客的列表之外的任何房间的房价。同样,其他顾客可以进行或取消房间预订。如果第一个顾客再次运行同样的查询,其他顾客的操作可能会影响他获得的可用房间列表。如果第一个顾客再次查询同一个日期段内的所有可用房间列表,产生的列表中有可能包含新的房价或第一次产生列表时不可用的房间。图10-7 说明了这种行为。

(点击查看大图)图10-7 读稳定性隔离级别的示例

在使用读稳定性隔离级别时,一个事务中所有被读取过的行上都会被加上NS锁,直到该事务被提交或回滚,行上的锁才会被释放。这样可以保证在一个事务中即使多次读取同一行,得到的值也不会改变。但是,如果使用这种隔离级别,在一个事务中,如果使用同样的搜索标准重新打开已被处理过的游标,则结果集可能改变(可能会增加某些行,这些行被称为幻影行(Phantom)。这是因为RS隔离级不能阻止通过插入或更新操作在结果集中加入新行)。

读稳定性(RS)只锁定应用程序在工作单元中检索的那些行。它确保在某个工作单元完成之前,在该工作单元运行期间的任何限定行读取不被其他应用程序进程更改,且确保不会读取由另一个应用程序进程所更改的任何行,直至该进程落实了这些更改。也就是说,不可能出现"不可重复读"情形。

"读稳定性"隔离级别的其中一个目标是提供较高并行性以及数据的稳定视图。为了有助于达到此目标,优化器确保在发生锁定升级前不获取表级锁定。

"读稳定性"隔离级别最适用于包括下列所有特征的应用程序:

在并发环境下运行

须要限定某些行在工作单元运行期间保持稳定

在工作单元中不会多次发出相同的查询,或者在同一工作单元中发出多次查询时并不要求该查询获得相同的回答

3。  游标稳定性(CS-Cursor Stability)

游标稳定性隔离级别在隔离事务效果方面非常宽松。它可以防止脏读,但有可能出现不可重复读和幻像读。这是因为在大多数情况下,游标稳定性隔离级别只锁定事务声明并打开的游标当前所引用的行。

当使用游标稳定性隔离级别的事务通过游标从表中检索行时,其他事务不能更新或删除游标所引用的行。但是,如果被锁定的行本身不是用索引访问的,那么其他事务可以将新的行添加到表中,以及对被锁定行前后的行进行更新和/或删除操作。所获取的锁一直有效,直到游标重定位或事务终止为止(如果游标重定位,原来行上的锁就被释放,并获得游标现在所引用行上的锁)。此外,如果事务修改了它检索到的任何行,那么在事务终止之前,其他事务不能更新或删除该行,即使游标不再位于被修改的行。与可重复读和读稳定性隔离级别一样,其他事务在其他行上进行的更改,在这些更改提交之前对于使用游标稳定性隔离级别(这是默认的隔离级别)的事务是不可见的。

如果如家酒店客房预定程序在游标稳定性隔离级别下运行,那么会有什么影响呢?当一个顾客检索某个日期段内所有可用房间的列表,然后查看产生的列表上每个房间的信息时(每次查看一个房间),您可以更改旅馆中任何房间的房价,但是这个顾客当前正在查看的房间除外(对于指定的日期段)。同样,其他顾客可以对任何房间进行或取消预订,但是这个顾客当前正在查看的房间除外(对于指定的日期段)。也就是说,对于第一个顾客当前正在查看的房间记录,您和其他顾客都不能进行任何操作。当第一个顾客查看列表中另一个房间的信息时,您和其他顾客就可以修改他刚才查看的房间记录(如果这个顾客没有预订这个房间的话)。图10-8说明了这种行为。

 
(点击查看大图)图10-8  游标稳定性隔离级别的示例

在使用游标稳定性隔离级别时,一个事务的结果集中只有正在被读取的那一行(游标指向的行)会被加上NS锁,其他未被处理的行上不被加锁。这种隔离级别只能保证正在被处理的行的值不会被其他并发的程序所改变。该隔离级别是DB2默认的隔离级别。

当在行上定位游标时,游标稳定性(CS)会锁定任何由应用程序的事务所访问的行。此锁定在读取下一行或终止事务之前有效。但是,如果更改了某一行上的任何数据,那么在对数据库落实更改之前必须挂起该锁定。

对于具有"游标稳定性"的应用程序已检索的行,当该行上有任何可更新的游标时,任何其他应用程序都不能更新或删除该行。"游标稳定性"应用程序不能查看其他应用程序的未落实更改。

使用"游标稳定性"隔离级别,可能会出现不可重复读和幻像读现象。"游标稳定性"是默认隔离级别,建议在需要最大并行性,但只看到其他应用程序中已落实行的情况下才使用。

4。 未提交读(UR-Uncommitted Read)

未提交读隔离级别是最不严格的隔离级别。实际上,在使用这个隔离级别时,仅当另一个事务试图删除或更改被检索的行所在的表时,才会锁定该检索的行。因为在使用这种隔离级别时,行通常保持未锁定状态,所以脏读、不可重复读和幻像读都可能会发生。因此,未提交读隔离级别通常用于那些访问只读表和视图的事务,以及某些执行SELECT语句的事务(只要其他事务的未提交数据对这些语句没有负面效果)。

顾名思义,其他事务对行所作的更改在被提交之前对于使用未提交读隔离级别的事务是可见的。但是,此类事务不能看见或访问其他事务所创建的表、视图或索引,直到那些事务被提交为止。类似地,如果其他事务删除了现有的表、视图或索引,那么仅当进行删除操作的事务终止时,使用未提交读隔离级别的事务才能知道这些对象不再存在了(一定要注意一点:当运行在未提交读隔离级别下的事务使用可更新游标时,该事务的行为和在游标稳定性隔离级别下运行一样,并应用游标稳定性隔离级别的约束)。

那么未提交读隔离级别对如家酒店客房预定应用程序有什么影响呢?现在,当一个顾客检索某个日期段内的所有可用房间列表时,您可以更改旅馆中任何房间在任何日期的房价,而其他顾客也可以对任何房间进行或取消预订,包括第一个顾客当前正在查看的房间记录(对于指定的日期段)。另外,第一个顾客生成的房间列表可能包含其他顾客正在预订(因此实际上不可用)的房间。图10-9说明了这种行为。

 
(点击查看大图)图10-9  未提交读隔离级别示例

未提交读(UR)允许应用程序访问其他事务的未提交的更改。除非其他应用程序尝试删除或改变该表,否则该应用程序不会锁定正读取的行而使其他应用程序不能访问该行。对于只读和可更新的游标,"未提交的读"的工作方式有所不同。

在使用未提交读隔离级别时,对于只读操作,不加行锁。典型的只读操作包括: SELECT语句的结果集(比如语句中包括ORDER BY子句);定义游标时指明FOR FETCH ONLY。

未提交读隔离级别可以改善应用程序的性能,同时可以最大程度地允许并发。但是,应用程序的数据完整性将受到威胁。如果需要读取未提交的数据,该隔离级是唯一选择。

只读游标可以访问大多数其他事务未落实的更改。但是,当该事务正在处理时,不能使用正由其他事务创建或删除的表、视图和索引。其他事务的任何其他更改在落实或回滚前都可被读取。

注意:

"未提交读"隔离级别下的可更新操作的游标的工作方式游标稳定性隔离级别下的相同。

当使用隔离级别UR运行程序时,应用程序可以使用隔离级别CS。发生这种情况的原因是应用程序中使用的游标是模糊游标。利用BLOCKING选项,可以将模糊游标升级为隔离级别CS。BLOCKING选项的默认值是UNAMBIG。这意味着模糊游标是可更新的,并且隔离级别可以升级为CS。要防止此升级,有两种选择:

修改应用程序中的游标为非模糊游标。将SELECT语句更改为包括FOR READ ONLY子句。

将模糊游标保留在应用程序中,但是使用预编译程序或BLOCKING ALL和STATICREADONLY YES选项绑定它,以允许在运行该程序时将任何模糊游标视为只读游标。

同对扫描1000行的"可重复读"给出的示例一样,如果使用"未提交读",那么不需要任何行锁定。

使用"未提交读",可能出现不可重复读和幻像读现象。"未提交读"隔离级别最常用于只读表上的查询。如果仅执行选择语句且不关心是否可从其他应用程序中看到未提交的数据时,那么该隔离级别也是不错的选择。

以上我们所讲的隔离级别的加锁范围和持续时间都是针对读操作而言的。对于更改操作,被修改的行上会被加上X锁,无论使用何种隔离级别,X锁都直到提交或回滚之后才会被释放。

5。  隔离级别的摘要

表10-7按不期望的结果概述了几个不同的隔离级别。

表10-7  隔离级别摘要

隔离级别

访问未落实的数据

不可重复读

幻像读现象

可重复读(RR)

不可能

不可能

不可能

读稳定性(RS)

不可能

不可能

可能

游标稳定性(CS)

不可能

可能

可能

未落实的读(UR)

可能

可能

可能

表10-8提供了简单的试探方法,以帮助您为应用程序选择初始隔离级别。首先考虑表10-8中所示的方法,并参阅先前对各隔离级别的讨论,以找到最适合的隔离级别。

表10-8  选择隔离级别的准则

应用程序类型

需要高数据稳定性

需要高数据稳定性

读写事务

RS

CS

只读事务

RR或RS

UR

为一个应用程序选择适当的隔离级别对于该应用程序避免无法容忍的现象很重要。因为获取和释放锁定所需的CPU和内存资源会随隔离级别的不同而不同,所以隔离级别不但影响应用程序之间的隔离程度,而且还影响个别应用程序的性能特征。潜在的锁等待情况也会随隔离级别的不同而不同。

因为隔离级别确定访问数据时如何锁定数据并使数据不受其他进程影响,所以您应该选择能平衡并行性和数据完整性需求的隔离级别。您指定的隔离级别在工作单元运行期间生效。

1. 选择正确的隔离级别

使用的隔离级别不仅影响数据库对并发性的支持程度,而且影响并发应用程序的性能。通常,使用的隔离级别越严格,并发性就越小,某些应用程序的性能还可能会越低,因为它们要等待资源上的锁被释放。那么,如何决定要使用哪种隔离级别呢?最好的方法是确定哪些现象是不可接受的,然后选择能够防止这些现象发生的隔离级别:

如果正在执行大型查询,而且不希望并发事务所作的修改会导致查询的多次运行而返回不同的结果,则使用可重复读隔离级别。

如果希望在应用程序之间获得一定的并发性,并且希望限定的行在事务执行期间保持稳定,则使用读稳定性隔离级别。

如果希望获得最大的并发性,同时不希望查询看到未提交的数据,则使用游标稳定性隔离级别。

如果正在只读的表/视图/数据库上执行查询,或者并不介意查询是否返回未提交的数据,则使用未提交读隔离级别。

2. 指定要使用的隔离级别

尽管隔离级别控制事务级上的行为,但实际上是在应用程序级被指定的:

对于嵌入式SQL应用程序,在预编译时或在将应用程序绑定到数据库(如果使用延迟绑定)时指定隔离级别。在这种情况下,使用PRECOMPILE或BIND命令的ISOLATION选项来设置隔离级别。

对于开放数据库连接(Open Database Connectivity,ODBC)和调用级接口(Call Level Interface,CLI)应用程序,隔离级别是在应用程序运行时通过调用指定了SQL_ATTR_TXN_ISOLATION连接属性的SQLSetConnectAttr()函数进行设置的(另外,也可以通过指定db2cli.ini 配置文件中的TXNISOLATION 关键字的值来设置ODBC/CLI应用程序的隔离级别。但是,这种方法不够灵活,不能像第一种方法那样为一个应用程序中的不同事务修改隔离级别)。

对于Java数据库连接(Java Database Connectivity,JDBC)和SQLJ应用程序,隔离级别是在应用程序运行时通过调用DB2的java.sql连接接口中的setTransactionIsolation() 方法设置的。

当没有使用这些方法显式指定应用程序的隔离级别时,默认使用游标稳定性隔离级别。这个默认设置适用于从命令行处理程序(CLP)执行的DB2命令、SQL语句和脚本,以及嵌入式SQL、ODBC/CLI、JDBC和SQLJ应用程序。因此,也可以为从CLP执行的操作(以及传递给DB2 CLP进行处理的脚本)指定隔离级别。在这种情况下,隔离级别是通过在建立数据库连接之前在CLP中执行CHANGE ISOLATION命令设置的:

 
 
  1. C:\pp>db2 change isolation to ur  
  2. DB21027E  当连接至数据库时未能更改隔离级别。  
  3. C:\pp>db2 connect reset  
  4. DB20000I  SQL命令成功完成。  
  5. C:\pp>db2 change isolation to ur  
  6. DB21053W  当连接至不支持 UR的数据库时,会发生自动升级。  
  7. DB20000I  CHANGE ISOLATION命令成功完成。 

在DB2 UDB 7.1及更高版本中,能够指定特定查询所用的隔离级别,方法是在SELECT SQL语句中加上WITH [RR | RS | CS | UR]子句。使用这个子句的简单SELECT语句示例如下所示:

 
 
  1. SELECT * FROM EMPLOYEE WHERE EMPID = '001' WITH RR 

如果应用程序在大多数时候需要比较宽松的隔离级别(以支持最大的并发性),但是对于其中的某些查询必须防止某些现象出现,那么这个子句就是帮助您实现目标的好方法。

七、发现死锁

1。WebSphere的SystemOut文件中出现 SQL0911N,或者SQLSTATE=40001,即出现了死锁

2。检查db2的快照,参考第6章介绍过的收集Db2快照的步骤:

 
 
  1. Locks held currently = 653  
  2. Lock waits = 14024  
  3. Time database waited on locks (ms) = 735625  
  4. Lock list memory in use (Bytes) = 32947760
  5. Deadlocks detected = 25  
  6. Lock escalations = 1049  
  7. Exclusive lock escalations = 647
  8. Agents currently waiting on locks = 21  
  9. Lock Timeouts = 352  
  10. Internal rollbacks due to deadlock = 0  
Deadlocks detected明确表示当前系统中出现了25个死锁,这就可以确认系统中发生了死锁问题。但这里还不清楚具体是哪些SQL语句发生了问题。
3。通过死锁监视器,确认在哪张表上发现了死锁
4。通过语句监视器,确认由于哪句SQL导致了死锁的发生

八、解决死锁

 1。首先应该检查系统调优参数

2。在排除参数调优问题之后,可以检查是否为数据库端的问题,包括标的实际或者SQL语句的设计

3。最后怀疑是否是应用程序编写的有问题,比如隔离级别的配置、业务逻辑实现方法等

4。db2的Locklist参数配置不当,导致频繁的锁升级发生

    (1) 在db2的快照中可以发现Lock escalations 这个代表锁升级发生的次数或者db2diag.log中可以查看详细锁升级时间信息,搜索关键字escalatio

   (2) 通过增加LOCKLIST和MAXLOCKS可以在一定程度上解决锁升级,这2个参数的调优可以根据如下的情况确定:

      A。 数据库级的快照:跟踪lock_list_in_use和locks_held已得到总体的锁资源的利用情况

     B。 应用程序的快照:跟踪locks_held以获得每个应用消耗的锁资源

     C。 事务级的快照:跟踪locks_held_top以获得该事物中的最高水位


九、死锁问题处理实例

1。RUNSTATS

    runstats的作用是收集数据库对象的状态信息,也就是更新数据库的各种统计信息。这对数据库优化器产生合理的访问计划至关重要。

    reorg的作用是通过对一个表进行压缩,重建行信息等消除斯upiand方法已达到重新组织表结构,降低访问开销的目的。

   所以应该首先对表进行reorg,然后再进行runstats。

2。缺少索引

3。sql语句不合理

4。资源访问顺序问题:比如级联删除问题,资源访问的顺序不当造成死锁

5。隔离级别问题:比如可以用with ur减少死锁的发生

6。意外更新记录:比如两个应用程序更改同一条记录


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值