このエントリは、JPOUG Advent Calendar 2016 の 21日目の記事です。 なお、昨日、20日目の記事は、守田 典男様の 12c DataGuard 遠隔同期インスタンスをつくってみた でした。
いつも少し時間が経つとオプティマイザ統計の遅延とSQL計画管理の機能がなんか似てて今一どう違うのかが忘れてしまうのでその備忘録として何度もアウトプットして出すことで忘れないようにしようと思い立ち、重い筆を持って書き連ねています。
大まかな概要や違いなどはDatabase Lounge Tokyo #3で以前発表させていただきました。
簡単に言えば、オプティマイザ統計の保留を使えばモテる!
さらに金さえあれば、SQL計画管理を使ってこの世の全ての女性を虜にできる!!
若干盛られている感がありますが、多分こんな感じです。
今回は発表では語りきれなかった実際のやり方について残しておこうかと思います。
発表資料に沿って、実際の実行計画とともに解説していこうと思います。
今回はmoteoユーザさんのmotemoteテーブルにて、統計情報が保留されることで実行計画がどう変わるかを見てみます。
まずはmoteoユーザに適当なmotemoteテーブルを作ります。
MOTEO@pdb1> create table motemote (motedo number);
Table created.
MOTEO@pdb1> create index idx_motemote on motemote(motedo);
Index created.
MOTEO@pdb1> insert into motemote values (1201);
1 row created.
MOTEO@pdb1> commit;
Commit complete.
※ちなみに1201を入れた理由としては12/01が私の誕生日だからです。忘れてもらっても構いませんが。
適当にモテ度を注入したところで統計情報を取得しておきます。
MOTEO@pdb1> exec dbms_stats.gather_table_stats('MOTEO', 'MOTEMOTE');
PL/SQL procedure successfully completed.
MOTEO@pdb1> select TABLE_NAME, LAST_ANALYZED from user_tab_statistics where TABLE_NAME='MOTEMOTE';
TABLE_NAME LAST_ANALYZED
------------------------------ -------------------
MOTEMOTE 2016-12-20 20:43:00
割りと時間ギリギリに執筆を始めてるのがもろわかりですね。
さて、ここで一旦実行計画を見てみます。
-- PLAN_TABLEを作っておく
MOTEO@pdb1> @?/rdbms/admin/utlxplan.sql
Table created.
MOTEO@pdb1> explain plan for select * from motemote where motedo = 1201;
Explained.
MOTEO@pdb1> @?/rdbms/admin/utlxplp.sql
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
Plan hash value: 2632143272
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 4 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IDX_MOTEMOTE | 1 | 4 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("MOTEDO"=1201)
13 rows selected.
「INDEX RANGE SCAN」でアクセスしていることがわかります。
※ちなみに、motedoカラムを1201の条件にした理由としては、12/01は私の誕生日だからです。もちろん忘れてもらっても構いませんが。
ここで取得した統計情報を信じ、一旦motemoteテーブルを保留とします。
-- デフォルトは公開状態
MOTEO@pdb1> select dbms_stats.get_prefs('PUBLISH', 'MOTEO', 'MOTEMOTE') publish from dual;
PUBLISH
----------
TRUE
-- 保留に変更
MOTEO@pdb1> exec dbms_stats.set_table_prefs('MOTEO', 'MOTEMOTE', 'PUBLISH', 'FALSE') ;
PL/SQL procedure successfully completed.
MOTEO@pdb1> select dbms_stats.get_prefs('PUBLISH', 'MOTEO', 'MOTEMOTE') publish from dual;
PUBLISH
----------
FALSE
この状態で実行計画が変わるような改革を起こして統計情報を取得しなおします。
MOTEO@pdb1> BEGIN
FOR I IN 1..1201 LOOP
insert into motemote values (1201);
END LOOP;
COMMIT;
END;
/
PL/SQL procedure successfully completed.
MOTEO@pdb1> exec dbms_stats.gather_table_stats('MOTEO', 'MOTEMOTE');
PL/SQL procedure successfully completed.
確変的データ量として1201行挿入してみました。
※ちなみに1201行にした理由としては12/01は私の誕生日だからです。忘れてもらっても構いませんが。
準備が終わったところで,早速保留の成果を確認してみましょう。魔法のおまじないとしてoptimizer_use_pending_statisticsパラメータをfalseにしておきます。(デフォルトはtrue)
その状態で実行計画を再度見てみます。
MOTEO@pdb1> alter session set optimizer_use_pending_statistics = false;
Session altered.
MOTEO@pdb1> explain plan for select * from motemote where motedo = 1201;
Explained.
MOTEO@pdb1> @?/rdbms/admin/utlxplp.sql
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
Plan hash value: 2632143272
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 4 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IDX_MOTEMOTE | 1 | 4 | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("MOTEDO"=1201)
13 rows selected.
大量にデータを入れた後に統計情報を取得したにも関わらず、新しい統計情報は使われず、見積もりも1行のままの古い統計情報で実行計画を実行しているのがわかります。
では、取得した統計情報はどこにいるのでしょうか。
それは、あなたの心の中…ではなく、「XXX_TAB_PENDING_STATS」に格納されています。
MOTEO@pdb1> select * from USER_TAB_PENDING_STATS;
TABLE_NAME PARTITION_NAME
------------------------------ ---------------
SUBPARTITION_NAME
------------------------------------------------------------------------------------------------------------------------
NUM_ROWS BLOCKS AVG_ROW_LEN SAMPLE_SIZE LAST_ANALYZED
---------- ---------- ----------- ----------- -------------------
MOTEMOTE
1202 2 4 1202 2016-12-20 23:23:37
きちんと1202行あることがわかりますね。
(どうでもいい情報ですが、私の誕生日は12/02ではないので要注意です)
では、この保留した統計情報を使うためにoptimizer_use_pending_statisticsをtrueにする呪文をかけ、その状態で実行計画を再度確認してみましょう。
-- 保留統計情報を使用する。
MOTEO@pdb1> alter session set optimizer_use_pending_statistics = true;
Session altered.
MOTEO@pdb1> explain plan for select * from motemote where motedo = 1201;
Explained.
MOTEO@pdb1> @?/rdbms/admin/utlxplp.sql
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
Plan hash value: 566102108
------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1202 | 4808 | 2 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| MOTEMOTE | 1202 | 4808 | 2 (0)| 00:00:01 |
------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("MOTEDO"=1201)
13 rows selected.
保留された統計情報を使っているので見積もりの行も1202行と一致し、それにあった実行計画になっていますね。
ちなみに索引が使われなくなったのは、1201のデータが多すぎるので索引を使った検索よりも全表検索の方が速いと判断されているためです。
誕生日アピールしすぎて無視されちゃいましたね。
保留の統計情報を確認してみて、問題なければDBMS_STATS.PUBLISH_PENDING_STATSを使用して公開することによって、その統計情報が使われるようになります。
MOTEO@pdb1> exec DBMS_STATS.PUBLISH_PENDING_STATS('MOTEO', 'MOTEMOTE');
PL/SQL procedure successfully completed.
MOTEO@pdb1> explain plan for select * from motemote where motedo = 1201;
Explained.
MOTEO@pdb1> @?/rdbms/admin/utlxplp.sql
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
Plan hash value: 566102108
------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1202 | 4808 | 2 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| MOTEMOTE | 1202 | 4808 | 2 (0)| 00:00:01 |
------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("MOTEDO"=1201)
13 rows selected.
公開したので、保留の統計情報は消えてなくなります。
MOTEO@pdb1> select * from USER_TAB_PENDING_STATS;
no rows selected
こんな感じで統計情報を取得しても一旦保留といった形で保管されるので、統計情報を取得したら性能が悪くなるんじゃないかという不安の解決や、新しい統計情報での実際の性能を気軽に確認したり、誕生日をアピールしすぎて周りから疎まれることができます。
次は、SQL計画管理について、実行計画履歴からSQL計画ベースラインにもってきて承認する様子を実際の実行計画とともに解説してみます。
今回は2016年 恋人にしたい男性有名人ランキングで見事1位にランクインした嵐の【相葉雅紀】さんを参考にもっとモテモテになるような方法を解説しています。
dbaのビューをみたり色々実行したりするので、MOTEOユーザには今回からDBAになってもらいます。
SYS@pdb1> grant dba to moteo;
Grant succeeded.
日本一モテル男テーブルを作成して適当にデータを入れます。
MOTEO@pdb1> create table aiba_masaki (motedo number);
Table created.
MOTEO@pdb1> insert into aiba_masaki values (1201);
1 row created.
MOTEO@pdb1> commit;
Commit complete.
※ちなみに、知っている方はご存知かもしれませんが、1201のデータを入れた理由は相葉さんの誕生日ではなく私の誕生日を入れています。
次にoptimizer_capture_sql_plan_baselinesをtrueにし、発行したSQLを片っ端からSQL計画ベースラインに登録していきます。
MOTEO@pdb1> alter session set optimizer_capture_sql_plan_baselines=true;
Session altered.
-- 1度目はSQL実行履歴に登録される
MOTEO@pdb1> select * from AIBA_MASAKI where motedo = 1201;
MOTEDO
----------
1201
-- 2度目はSQL計画ベースラインに登録がないので登録される
MOTEO@pdb1> select * from AIBA_MASAKI where motedo = 1201;
MOTEDO
----------
1201
MOTEO@pdb1> alter session set optimizer_capture_sql_plan_baselines=false;
Session altered.
MOTEO@pdb1> select signature, sql_handle, sql_text, plan_name, origin, enabled, accepted, fixed, autopurge from dba_sql_plan_baselines where sql_text like '%1201%';
SIGNATURE SQL_HANDLE SQL_TEXT
---------- ------------------------------ --------------------------------------------------
PLAN_NAME ORIGIN ENABLED ACCEPTED FIXED AUTOPURGE
------------------------------ -------------------- --------- --------- --------- ---------
1.3340E+19 SQL_b9222bb840ec721d select * from AIBA_MASAKI where motedo = 1201
SQL_PLAN_bk8jbr10fswhx204adfd1 AUTO-CAPTURE YES YES NO YES
dba_sql_plan_baselinesビューの中を見るときに、私の誕生日(12月1日)でもある1201をキーワードとして引っ掛けることで、いい感じにmotedoが1201を検索しているイカしたSQLが登録されていることがわかります。
次に相葉ちゃんに大量にファンが増えてmotedoが急上昇しても軽く処理ができるように、索引を作成し再度、初回とは別の実行計画をSQL計画ベースラインに載せようとしてみます。
MOTEO@pdb1> create index idx_aiba_masaki on aiba_masaki(motedo);
Index created.
MOTEO@pdb1> alter session set optimizer_capture_sql_plan_baselines=true;
Session altered.
MOTEO@pdb1> select * from AIBA_MASAKI where motedo = 1201;
MOTEDO
----------
1201
MOTEO@pdb1> alter session set optimizer_capture_sql_plan_baselines=false;
Session altered.
MOTEO@pdb1> select signature, sql_handle, sql_text, plan_name, origin, enabled, accepted, fixed, autopurge from dba_sql_plan_baselines where sql_text like '%1201%';
SIGNATURE SQL_HANDLE SQL_TEXT
---------- ------------------------------ --------------------------------------------------
PLAN_NAME ORIGIN ENABLED ACCEPTED FIXED AUTOPURGE
------------------------------ -------------------- --------- --------- --------- ---------
1.3340E+19 SQL_b9222bb840ec721d select * from AIBA_MASAKI where motedo = 1201
SQL_PLAN_bk8jbr10fswhx204adfd1 AUTO-CAPTURE YES YES NO YES
1.3340E+19 SQL_b9222bb840ec721d select * from AIBA_MASAKI where motedo = 1201
SQL_PLAN_bk8jbr10fswhx8afe516d AUTO-CAPTURE YES NO NO YES
2つめの実行計画も無事SQL計画ベースラインにのりましたが、ACCEPTEDカラムがNOで承認されていないことがわかります。
準備ができたところでoptimizer_use_sql_plan_baselinesをtrueにし、SQL計画ベースラインにある実行計画のみを使用するように設定し、実行計画を見てみます。
MOTEO@pdb1> alter session set optimizer_use_sql_plan_baselines = true;
Session altered.
MOTEO@pdb1> explain plan for select * from AIBA_MASAKI where motedo = 1201;
Explained.
MOTEO@pdb1> @?/rdbms/admin/utlxplp.sql
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
Plan hash value: 4020717252
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 13 | 2 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| AIBA_MASAKI | 1 | 13 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("MOTEDO"=1201)
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- SQL plan baseline "SQL_PLAN_bk8jbr10fswhx204adfd1" used for this statement
18 rows selected.
索引は作成したはずなのに最初にSQL計画ベースライン記録した全表検索での実行計画を使用していることがわかります。
Noteにも「SQL plan baseline “SQL_PLAN_bk8jbr10fswhx204adfd1” used for this statement」と出ていてさらにSQL計画ベースラインの実行計画を使っているのがわかりますね。
後から記録された実行計画は承認がされない限りオプティマイザが使ってくれなくなります。
今度は索引作成後に作成された実行計画を承認して使えるようにしましょう。
以下のような、流れで承認をすることが出来ます。
展開タスク作成→展開タスク実行→タスクの結果をレポート→タスク内の推奨事項を実装
ということで早速、未承認の実行計画のレポートを表示してみましょう
MOTEO@pdb1> VARIABLE cnt NUMBER
MOTEO@pdb1> VARIABLE tk_name VARCHAR2(50)
MOTEO@pdb1> VARIABLE exe_name VARCHAR2(50)
MOTEO@pdb1> VARIABLE evol_out CLOB
-- タスク作成
MOTEO@pdb1> EXECUTE :tk_name := DBMS_SPM.CREATE_EVOLVE_TASK( sql_handle => 'SQL_b9222bb840ec721d', plan_name => 'SQL_PLAN_bk8jbr10fswhx8afe516d');
PL/SQL procedure successfully completed.
MOTEO@pdb1> SELECT :tk_name FROM DUAL;
:TK_NAME
------------------------------------------------------------------------------------------------------------------------
TASK_11
-- タスク実行
MOTEO@pdb1> EXECUTE :exe_name :=DBMS_SPM.EXECUTE_EVOLVE_TASK(task_name=>:tk_name);
PL/SQL procedure successfully completed.
MOTEO@pdb1> SELECT :exe_name FROM DUAL;
:EXE_NAME
------------------------------------------------------------------------------------------------------------------------
EXEC_21
-- レポート表示
MOTEO@pdb1> EXECUTE :evol_out := DBMS_SPM.REPORT_EVOLVE_TASK( task_name=>:tk_name, execution_name=>:exe_name );
PL/SQL procedure successfully completed.
MOTEO@pdb1> set long 9999
MOTEO@pdb1> col :EVOL_OUT for a100
MOTEO@pdb1> SELECT :evol_out FROM DUAL;
:EVOL_OUT
----------------------------------------------------------------------------------------------------
GENERAL INFORMATION SECTION
-----------------------------------------------------------------------
----------------------
Task Information:
---------------------------------------------
Task Name : TASK_11
Task Owner : MOTEO
Execution Name : EXEC_21
Execution Type : SPM EVOLVE
Scope : COMPREHENSIVE
Status : COMPLETED
Started : 12/21/2016 13:23:23
Finished : 12/21/2016 13:23:24
Last Updated : 12/21/2016 13:23:24
Global Time Limit : 2147483646
Per-Plan Time Limit : UNUSED
Number of Errors : 0
------------------------------------------------------------------
---------------------------
SUMMARY SECTION
---------------------------------------------------------------------------------------
------
Number of plans processed : 1
Number of findings : 1
Number of recommendations : 1
Number of errors : 0
------------------------------------------------------------------------------------
---------
DETAILS SECTION
------------------------------------------------------------------------
---------------------
Object ID : 2
Test Plan Name : SQL_PLAN_bk8jbr10fswhx8afe516d
Base Plan Name : SQL_PLAN_bk8jbr10fswhx204adfd1
SQL Handle : SQL_b9222bb840ec721d
Parsing Schema : MOTEO
Test Plan Creator : MOTEO
SQL Text : select * from AIBA_MASAKI where motedo = 1201
Execution Statistics:
-----------------------------
Base Plan Test Plan
---------------------------- --------------------
--------
Elapsed Time (s): .000003 .000004
CPU Time (s): 0 0
Buffer Gets: 0 0
Optimizer Cost: 2 1
Disk Reads: 0 0
Direct Writes: 0 0
Rows Processed: 0 0
Executions: 10 10
FINDINGS SECTION
------------------------------------------------------------------------------------------
---
Findings (1):
-----------------------------
1. The plan was verified in 0.07000 seconds. It passed the benefit criterion
because its verified performance was 3.00000 times better than t
hat of the
baseline plan.
Recommendation:
-----------------------------
Consider accepting the plan. Execute
dbms_spm.accept_sql_plan_baseline(task_name => 'TASK_11', object_id => 2,
task_owner => 'MOTEO');
EXPLAIN PLANS SECTION
---------------------------------------------------------------------------------------
------
Baseline Plan
-----------------------------
Plan Id : 1
Plan Hash Value : 541777873
----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time
|
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 13 | 2 | 00:00:01 |
| * 1 | TABLE ACCESS FULL | AIBA_MASAKI | 1 | 13 | 2 | 00:00:
01 |
----------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------
* 1 - filter("MOTEDO"=1201)
Note
-----
- dynamic sampling used for this statement
Test Plan
-----------------------------
Plan Id : 2
Plan Hash Value : 2331922797
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost | Time |
------------------------------------------------------------------
-------------
| 0 | SELECT STATEMENT | | 1 | 13 | 1 | 00:00:01 |
| * 1 | INDEX RANGE SCAN | IDX_AIBA_MASAKI | 1 | 13 | 1 | 00
:00:01 |
-------------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------
* 1 - access("MOTEDO"=1201)
Note
-----
- dynamic sampling used for this statement
---------------------------------------------------------------------------------------------
ぶわっとレポートが出ますが、以下の様なことが言われているのがわかります。
Findings (1):
-----------------------------
1. The plan was verified in 0.07000 seconds. It passed the benefit criterion
because its verified performance was 3.00000 times better than that of the
baseline plan.
簡単に言うと検証した実行計画は今よりよくなるよ!と教えてくれてます。
ということで、推奨にもある通り、承認のプロセスを実行してみましょう。
MOTEO@pdb1> exec dbms_spm.accept_sql_plan_baseline(task_name => 'TASK_11', object_id => 2, task_owner => 'MOTEO');
PL/SQL procedure successfully completed.
MOTEO@pdb1> select signature, sql_handle, sql_text, plan_name, origin, enabled, accepted, fixed, autopurge from dba_sql_plan_baselines where plan_name = 'SQL_PLAN_bk8jbr10fswhx8afe516d';
SIGNATURE SQL_HANDLE SQL_TEXT
---------- ------------------------------ --------------------------------------------------
PLAN_NAME ORIGIN ENABLED ACCEPTED FIXED AUTOPURGE
------------------------------ -------------------- --------- --------- --------- ---------
1.3340E+19 SQL_b9222bb840ec721d select * from AIBA_MASAKI where motedo = 1201
SQL_PLAN_bk8jbr10fswhx8afe516d AUTO-CAPTURE YES YES NO YES
ACCEPTEDがYESになって承認されました。
この状態で、実行計画を見てみましょう。
MOTEO@pdb1> explain plan for select * from AIBA_MASAKI where motedo = 1201;
Explained.
MOTEO@pdb1> @?/rdbms/admin/utlxplp.sql
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
Plan hash value: 2396022278
------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 13 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IDX_AIBA_MASAKI | 1 | 13 | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("MOTEDO"=1201)
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
- SQL plan baseline "SQL_PLAN_bk8jbr10fswhx8afe516d" used for this statement
18 rows selected.
無事承認された実行計画を使ってくれるようになりました!
ここまで手動で承認のフローを解説してきましたが、12cよりなんと!
自動で既存の承認済の計画よりもパフォーマンスが高い場合に展開をしてくれるSPM展開アドバイザというタスクが追加されました!! (パチパチ)
自動SQLチューニング・タスクが実行される時についでに実行されているようです。
何もしなくてもいい感じによしなに承認してくれるのでいい時代になりましたね。
今回は、optimizer_capture_sql_plan_baselinesを用いてSQL計画ベースラインにロードしましたが、ロードする方法としては他にもSQLチューニング・セット、共有SQL領域、ステージング表、ストアド・アウトラインなどからロードすることが可能です。
cf: SQL計画ベースラインのロード
これを使えばいい感じに良い実行計画だけを扱えるようになりますが、注意してほしいのがこれはEnterpriseEditionのみの機能になるのでご注意下さい。
あと、あまりSQL計画管理では誕生日のアピールができていませんでしたが、12月1日は私の誕生日です。
もちろん皆さん明日には忘れると思います。
そんなこんなで統計情報の保留とSQL計画管理を使えばきっとみなさんモテモテになると思いますので明日から是非機会があれば使ってみて下さい。
最後にここで皆さんにお知らせがあります。
実は、
今月の1日、
.
…
はい、つまり12月1日は・・・
.
..
…
みなさんびっくりしたかもしれませんね。
もしよろしかったら私のほしいものリストから是非ロイスを買って下さい。
ブラさんはたくさんもらったのでもうお腹いっぱいです。
明日は、mutatsuさんの記事になります。
みなさん、今年も良いお年とクリスマスと誕生日をお送り下さい。
こんにちは。virapture…
View Comments
I am an investor of gate io, I have consulted a lot of information, I hope to upgrade my investment strategy with a new model. Your article creation ideas have given me a lot of inspiration, but I still have some doubts. I wonder if you can help me? Thanks.
Can you be more specific about the content of your article? After reading it, I still have some doubts. Hope you can help me.
Your article helped me a lot, is there any more related content? Thanks!
Can you be more specific about the content of your article? After reading it, I still have some doubts. Hope you can help me. https://www.binance.info/vi/join?ref=S5H7X3LP
Your point of view caught my eye and was very interesting. Thanks. I have a question for you.
Thank you for your sharing. I am worried that I lack creative ideas. It is your article that makes me full of hope. Thank you. But, I have a question, can you help me?