052011GR2 _optimizer_null_aware_antijoin

简介: [20150520]11GR2 _optimizer_null_aware_antijoin.txt --好久没写sql 优化的帖子: http://www.killdb.

[20150520]11GR2 _optimizer_null_aware_antijoin.txt

--好久没写sql 优化的帖子:

http://www.killdb.com/2015/04/19/_optimizer_null_aware_antijoin%E5%BC%95%E5%8F%91%E7%9A%84sql%E6%80%A7%E8%83%BD%E9%97%AE%E9%A2%98.html

--参考这个链接自己重复测试看看在11G下的情况:

1.建立测试环境:

SCOTT@test> @ver1
PORT_STRING                    VERSION        BANNER
------------------------------ -------------- --------------------------------------------------------------------------------
x86_64/Linux 2.4.xx            11.2.0.3.0     Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production

create table t1
as select
cast(rownum as int) a,
cast(rownum+10 as int) b,
cast(dbms_random.string('i',10) as varchar2(10)) c
from dual connect by level 
create table t2
as select
cast(rownum as int) a,
cast(rownum+10 as int) b,
cast(dbms_random.string('i',10) as varchar2(10)) c
from dual connect by level

exec sys.dbms_stats.gather_table_stats ( OwnName => user,TabName => 't1',Estimate_Percent => NULL,Method_Opt => 'FOR ALL COLUMNS SIZE 1 ',Cascade => True ,No_Invalidate => false)
PL/SQL procedure successfully completed.

exec sys.dbms_stats.gather_table_stats ( OwnName => user,TabName => 't2',Estimate_Percent => NULL,Method_Opt => 'FOR ALL COLUMNS SIZE 1 ',Cascade => True ,No_Invalidate => false)
PL/SQL procedure successfully completed.

2.测试:

select /*SQL_1*/ c from t1 where a not in (select a from t2) ;

SCOTT@test> @dpc '' ''
PLAN_TABLE_OUTPUT
-------------------------------------
SQL_ID  1war6t5g1w5g9, child number 0
-------------------------------------
select /*SQL_1*/ c from t1 where a not in (select a from t2)

Plan hash value: 895956251

---------------------------------------------------------------------------------------------------------------------
| Id  | Operation          | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |      1 |        |       | 40633 (100)|          |     20 |00:00:03.69 |     190K|
|*  1 |  FILTER            |      |      1 |        |       |            |          |     20 |00:00:03.69 |     190K|
|   2 |   TABLE ACCESS FULL| T1   |      1 |  10000 |   146K|     8   (0)| 00:00:01 |  10000 |00:00:00.01 |      37 |
|*  3 |   TABLE ACCESS FULL| T2   |  10000 |      1 |     4 |     8   (0)| 00:00:01 |   9980 |00:00:03.62 |     190K|
---------------------------------------------------------------------------------------------------------------------

Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------

   1 - SEL$1
   2 - SEL$1 / T1@SEL$1
   3 - SEL$2 / T2@SEL$2

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter( IS NULL)
   3 - filter(LNNVL("A":B1))

--使用filter,逻辑读高达190K,效率低下.一般这种语句我会改写成not exists测试:

SCOTT@test> @dpc '' ''
PLAN_TABLE_OUTPUT
-------------------------------------
SQL_ID  36gqqz0c26g8d, child number 0
-------------------------------------
select /*SQL_1*/ c from t1 where  not exists  (select a from t2 where
t2.a=t1.a)
Plan hash value: 629543484
--------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation            | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
--------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |      |      1 |        |       |    17 (100)|          |     20 |00:00:00.08 |      73 |       |       |          |
|*  1 |  HASH JOIN RIGHT ANTI|      |      1 |     20 |   380 |    17   (6)| 00:00:01 |     20 |00:00:00.08 |      73 |  1517K|  1517K| 1872K (0)|
|   2 |   TABLE ACCESS FULL  | T2   |      1 |   9980 | 39920 |     8   (0)| 00:00:01 |   9980 |00:00:00.01 |      36 |       |       |          |
|   3 |   TABLE ACCESS FULL  | T1   |      1 |  10000 |   146K|     8   (0)| 00:00:01 |  10000 |00:00:00.01 |      37 |       |       |          |
--------------------------------------------------------------------------------------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
   1 - SEL$5DA710D3
   2 - SEL$5DA710D3 / T2@SEL$2
   3 - SEL$5DA710D3 / T1@SEL$1
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - access("T2"."A"="T1"."A")

--很明显逻辑读73,以前有许多帖子讨论not in 与not exists那个好那个差,实际上现在许多情况下根本不需要考虑这些细节,oracle的查
--询转化很好的选择最优的执行方式.

3.使用sql profile看看:
--我写的脚本:
$ cat sp1.sql
set verify off
set long 20000000
set longchunksize  20000000
column report_tuning_task format a300
declare
  a varchar2(200);
begin
  a := dbms_sqltune.create_tuning_task(task_name=>'tuning &1',description=>'tuning sql_id=&1',scope=>dbms_sqltune.scope_comprehensive,time_limit=>1800,sql_id=>'&1');
  dbms_sqltune.execute_tuning_task( a );
end;
/

prompt
prompt =================================================================================================================================================
prompt tuning sql_id=&1 : report
prompt =================================================================================================================================================
select dbms_sqltune.report_tuning_task('tuning &1') report_tuning_task FROM dual;

prompt =================================================================================================================================================
prompt if finished,drop tuning task , run:
prompt execute dbms_sqltune.drop_tuning_task('tuning &1')
prompt if accept sql profile, run:
prompt execute dbms_sqltune.accept_sql_profile(task_name => 'tuning &1', replace => TRUE ,name=>'tuning &1');;
prompt execute dbms_sqltune.accept_sql_profile(task_name => 'tuning &1', replace => TRUE, name=>'tuning &1', FORCE_MATCH=>True)
prompt if drop or alter sql profile ,run :
prompt execute dbms_sqltune.drop_sql_profile(name => 'tuning &1')
prompt execute dbms_sqltune.alter_sql_profile(name => 'tuning &1',attribute_name=>'STATUS',value=>'DISABLED')
prompt =================================================================================================================================================
prompt
prompt
set serveroutput off


SCOTT@test> @sp1 1war6t5g1w5g9
PL/SQL procedure successfully completed.

=================================================================================================================================================
tuning sql_id=1war6t5g1w5g9 : report
=================================================================================================================================================
REPORT_TUNING_TASK
-------------------------------------------------------------------------------
GENERAL INFORMATION SECTION
-------------------------------------------------------------------------------
Tuning Task Name   : tuning 1war6t5g1w5g9
Tuning Task Owner  : SCOTT
Workload Type      : Single SQL Statement
Scope              : COMPREHENSIVE
Time Limit(seconds): 1800
Completion Status  : COMPLETED
Started at         : 05/20/2015 09:35:56
Completed at       : 05/20/2015 09:36:03

-------------------------------------------------------------------------------
Schema Name: SCOTT
SQL ID     : 1war6t5g1w5g9
SQL Text   : select /*SQL_1*/ c from t1 where a not in (select a from t2)

-------------------------------------------------------------------------------
FINDINGS SECTION (2 findings)
-------------------------------------------------------------------------------

1- SQL Profile Finding (see explain plans section below)
--------------------------------------------------------
  A potentially better execution plan was found for this statement.

  Recommendation (estimated benefit: 99.95%)
  ------------------------------------------
  - Consider accepting the recommended SQL profile.
    execute dbms_sqltune.accept_sql_profile(task_name => 'tuning
            1war6t5g1w5g9', task_owner => 'SCOTT', replace => TRUE);

  Validation results
  ------------------
  The SQL profile was tested by executing both its plan and the original plan
  and measuring their respective execution statistics. A plan may have been
  only partially executed if the other could be run to completion in less time.

                           Original Plan  With SQL Profile  % Improved
                           -------------  ----------------  ----------
  Completion Status:            COMPLETE          COMPLETE
  Elapsed Time (s):            3.733955           .082431      97.79 %
  CPU Time (s):                3.734431           .082387      97.79 %
  User I/O Time (s):                  0                 0
  Buffer Gets:                   190911                72      99.96 %
  Physical Read Requests:             0                 0
  Physical Write Requests:            0                 0
  Physical Read Bytes:                0                 0
  Physical Write Bytes:               0                 0
  Rows Processed:                    20                20
  Fetches:                           20                20
  Executions:                         1                 1

  Notes
  -----
  1. Statistics for the original plan were averaged over 1 executions.
  2. Statistics for the SQL profile plan were averaged over 10 executions.

2- Restructure SQL finding (see plan 1 in explain plans section)
----------------------------------------------------------------
  The optimizer could not unnest the subquery at line ID 1 of the execution
  plan.

  Recommendation
  --------------
  - Consider replacing "NOT IN" with "NOT EXISTS" or ensure that columns used
    on both sides of the "NOT IN" operator are declared "NOT NULL" by adding
    either "NOT NULL" constraints or "IS NOT NULL" predicates.

-------------------------------------------------------------------------------
EXPLAIN PLANS SECTION
-------------------------------------------------------------------------------

1- Original With Adjusted Cost
------------------------------
Plan hash value: 895956251

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |  9999 |   146K| 40633   (2)| 00:00:01 |
|*  1 |  FILTER            |      |       |       |            |          |
|   2 |   TABLE ACCESS FULL| T1   | 10000 |   146K|     8   (0)| 00:00:01 |
|*  3 |   TABLE ACCESS FULL| T2   |     1 |     4 |     8   (0)| 00:00:01 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter( NOT EXISTS (SELECT /*+ FULL ("T2") */ 0 FROM "T2" "T2"
              WHERE LNNVL("A":B1)))
   3 - filter(LNNVL("A":B1))

2- Using SQL Profile
--------------------
Plan hash value: 2739594415

--------------------------------------------------------------------------------
| Id  | Operation               | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |      |   100 |  1900 |    17   (6)| 00:00:01 |
|*  1 |  HASH JOIN RIGHT ANTI NA|      |   100 |  1900 |    17   (6)| 00:00:01 |
|   2 |   TABLE ACCESS FULL     | T2   |  9980 | 39920 |     8   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL     | T1   | 10000 |   146K|     8   (0)| 00:00:01 |
--------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("A"="A")

-------------------------------------------------------------------------------

=================================================================================================================================================
if finished,drop tuning task , run:
execute dbms_sqltune.drop_tuning_task('tuning 1war6t5g1w5g9')
if accept sql profile, run:
execute dbms_sqltune.accept_sql_profile(task_name => 'tuning 1war6t5g1w5g9', replace => TRUE ,name=>'tuning 1war6t5g1w5g9');
execute dbms_sqltune.accept_sql_profile(task_name => 'tuning 1war6t5g1w5g9', replace => TRUE, name=>'tuning 1war6t5g1w5g9', FORCE_MATCH=>True)
if drop or alter sql profile ,run :
execute dbms_sqltune.drop_sql_profile(name => 'tuning 1war6t5g1w5g9')
execute dbms_sqltune.alter_sql_profile(name => 'tuning 1war6t5g1w5g9',attribute_name=>'STATUS',value=>'DISABLED')
=================================================================================================================================================

--注意看
  Recommendation
  --------------
  - Consider replacing "NOT IN" with "NOT EXISTS" or ensure that columns used
    on both sides of the "NOT IN" operator are declared "NOT NULL" by adding
    either "NOT NULL" constraints or "IS NOT NULL" predicates.

--注意看以上提示,再次说明一些约束对控制执行计划的好处执行计划.执行如下稳定执行计划:

SCOTT@test> execute dbms_sqltune.accept_sql_profile(task_name => 'tuning 1war6t5g1w5g9', replace => TRUE ,name=>'tuning 1war6t5g1w5g9');
PL/SQL procedure successfully completed.

SCOTT@test> select /*SQL_1*/ c from t1 where  not exists  (select a from t2 where t2.a=t1.a) ;

SCOTT@test> @dpc '' ''
PLAN_TABLE_OUTPUT
-------------------------------------
SQL_ID  1war6t5g1w5g9, child number 0
-------------------------------------
select /*SQL_1*/ c from t1 where a not in (select a from t2)
Plan hash value: 2739594415
-----------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation               | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
-----------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |      |      1 |        |       |    17 (100)|          |     20 |00:00:00.08 |      73 |       |       |          |
|*  1 |  HASH JOIN RIGHT ANTI NA|      |      1 |    100 |  1900 |    17   (6)| 00:00:01 |     20 |00:00:00.08 |      73 |  1517K|  1517K| 1852K (0)|
|   2 |   TABLE ACCESS FULL     | T2   |      1 |   9980 | 39920 |     8   (0)| 00:00:01 |   9980 |00:00:00.01 |      36 |       |       |          |
|   3 |   TABLE ACCESS FULL     | T1   |      1 |  10000 |   146K|     8   (0)| 00:00:01 |  10000 |00:00:00.01 |      37 |       |       |          |
-----------------------------------------------------------------------------------------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
   1 - SEL$5DA710D3
   2 - SEL$5DA710D3 / T2@SEL$2
   3 - SEL$5DA710D3 / T1@SEL$1
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - access("A"="A")
Note
-----
   - SQL profile tuning 1war6t5g1w5g9 used for this statement

--说明SQL PROFILE已经起作用.注意一些细节,这个执行计划与not exists是有一点小小的不同.HASH JOIN RIGHT ANTI NA.

4.看看sql profile使用的提示:

SCOTT@test> @hide _optimizer_null_aware_antijoin
NAME                            DESCRIPTION                    DEFAULT_VALUE  SESSION_VALUE  SYSTEM_VALUE
------------------------------- ------------------------------ -------------- -------------- -------------
_optimizer_null_aware_antijoin  null-aware antijoin parameter  TRUE           FALSE          FALSE


$ cat spext.sql
/* Formatted on 2015/4/10 17:03:49 (QP5 v5.252.13127.32867) */
column hint format a150
column name format a30
SELECT EXTRACTVALUE (VALUE (h), '.') AS hint,so.name
  FROM SYS.sqlobj$data od
      ,SYS.sqlobj$ so
      ,TABLE
       (
          XMLSEQUENCE
          (
             EXTRACT (XMLTYPE (od.comp_data), '/outline_data/hint')
          )
       ) h
WHERE     so.NAME in ( 'profile &&1', 'tuning &&1','switch tuning &&1')
       AND so.signature = od.signature
       AND so.CATEGORY = od.CATEGORY
       AND so.obj_type = od.obj_type
       AND so.plan_id = od.plan_id;


SCOTT@test> @spext 1war6t5g1w5g9
HINT                                   NAME
-------------------------------------- ------------------------------
OPTIMIZER_FEATURES_ENABLE(default)     tuning 1war6t5g1w5g9

--昏,提示仅仅是OPTIMIZER_FEATURES_ENABLE(default).

5.最后测试作者提到的隐含参数:

--drop sql profile.

SCOTT@test> execute dbms_sqltune.drop_sql_profile(name => 'tuning 1war6t5g1w5g9')
PL/SQL procedure successfully completed.

SCOTT@test> alter session set "_optimizer_null_aware_antijoin"=true;
Session altered.

SCOTT@test> @dpc '' ''
PLAN_TABLE_OUTPUT
--------------------------------------
SQL_ID  1war6t5g1w5g9, child number 1
-------------------------------------
select /*SQL_1*/ c from t1 where a not in (select a from t2)
Plan hash value: 2739594415
-----------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation               | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
-----------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |      |      1 |        |       |    17 (100)|          |     20 |00:00:00.08 |      73 |       |       |          |
|*  1 |  HASH JOIN RIGHT ANTI NA|      |      1 |     20 |   380 |    17   (6)| 00:00:01 |     20 |00:00:00.08 |      73 |  1517K|  1517K| 1886K (0)|
|   2 |   TABLE ACCESS FULL     | T2   |      1 |   9980 | 39920 |     8   (0)| 00:00:01 |   9980 |00:00:00.01 |      36 |       |       |          |
|   3 |   TABLE ACCESS FULL     | T1   |      1 |  10000 |   146K|     8   (0)| 00:00:01 |  10000 |00:00:00.01 |      37 |       |       |          |
-----------------------------------------------------------------------------------------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
   1 - SEL$5DA710D3
   2 - SEL$5DA710D3 / T2@SEL$2
   3 - SEL$5DA710D3 / T1@SEL$1
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - access("A"="A")

SCOTT@test> alter session set "_optimizer_null_aware_antijoin"=false;
Session altered.

6.加入非NULL看看情况如何?

SCOTT@test> alter table t1 modify a not null;
Table altered.

SCOTT@test> alter table t2 modify a not null;
Table altered.

SCOTT@test> select /*SQL_1*/ c from t1 where a not in (select a from t2) ;

SCOTT@test> @dpc '' ''
PLAN_TABLE_OUTPUT
--------------------------------------
SQL_ID  1war6t5g1w5g9, child number 0
-------------------------------------
select /*SQL_1*/ c from t1 where a not in (select a from t2)
Plan hash value: 629543484
--------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation            | Name | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
--------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |      |      1 |        |       |    17 (100)|          |     20 |00:00:00.08 |      73 |       |       |          |
|*  1 |  HASH JOIN RIGHT ANTI|      |      1 |     20 |   380 |    17   (6)| 00:00:01 |     20 |00:00:00.08 |      73 |  1517K|  1517K| 1830K (0)|
|   2 |   TABLE ACCESS FULL  | T2   |      1 |   9980 | 39920 |     8   (0)| 00:00:01 |   9980 |00:00:00.01 |      36 |       |       |          |
|   3 |   TABLE ACCESS FULL  | T1   |      1 |  10000 |   146K|     8   (0)| 00:00:01 |  10000 |00:00:00.01 |      37 |       |       |          |
--------------------------------------------------------------------------------------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
   1 - SEL$5DA710D3
   2 - SEL$5DA710D3 / T2@SEL$2
   3 - SEL$5DA710D3 / T1@SEL$1
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - access("A"="A")

--注意id1:HASH JOIN RIGHT ANTI.

--总结:
--可以发现选择很好的执行计划,正像作者讲的那样,数据结构设计在这里起了很关键的作用,如果在一些细节上处理好,就可以避免许多性
--能问题.另外注意not in,not exists,或者in,exist的语句里面的sql语句,如果存在性能问题,可以试着在这些字段上确定是否为非空,
--加入一些约束也许能改变执行机会,取得好的优化效果.

目录
相关文章
|
5月前
|
机器学习/深度学习 数据挖掘
【提示学习】HPT: Hierarchy-aware Prompt Tuning for Hierarchical Text Classification
本文是较早把Prompt应用到层级多标签文本分类领域的论文。思路是把层级标签分层编入到Pattern中,然后修改损失函数以适应多标签的分类任务。
|
7月前
|
机器学习/深度学习 自然语言处理 算法
Joint Information Extraction with Cross-Task and Cross-Instance High-Order Modeling 论文解读
先前的信息抽取(IE)工作通常独立地预测不同的任务和实例(例如,事件触发词、实体、角色、关系),而忽略了它们的相互作用,导致模型效率低下。
54 0
|
7月前
|
机器学习/深度学习 存储 数据挖掘
Global Constraints with Prompting for Zero-Shot Event Argument Classification 论文解读
确定事件论元的角色是事件抽取的关键子任务。大多数以前的监督模型都利用了昂贵的标注,这对于开放域应用程序是不实际的。
49 0
|
机器学习/深度学习 算法 数据挖掘
【多标签文本分类】Improved Neural Network-based Multi-label Classification with Better Initialization ……
【多标签文本分类】Improved Neural Network-based Multi-label Classification with Better Initialization ……
【多标签文本分类】Improved Neural Network-based Multi-label Classification with Better Initialization ……
sbs
|
存储 SQL 人工智能
The Volcano Optimizer Generator: Extensibility and Efficient Search 论文翻译
原文:The Volcano Optimizer Generator: Extensibility and Efficient SearchThe Volcano Optimizer Generator: Extensibility and Efficient Search 论文翻译。2023.01.25 —— by zz【中括号内为译者注】对原文部分关键术语,或重点句有加粗。便于定位。为了避免英
sbs
164 0
The Volcano Optimizer Generator: Extensibility and Efficient Search 论文翻译
|
数据可视化 算法 数据挖掘
Evaluation of Clustering|学习笔记
快速学习 Evaluation of Clustering
111 0
Evaluation of Clustering|学习笔记
|
算法 大数据 数据库
The Volcano Optimizer Generator : Extensibility and Efficient Search
数据库查询优化器的搜索技术,基本上分为了基于动态规划的bottom-up search(详见文章System-R论文解读)和基于Cascades/Volcano的top-down search两个流派,这篇文章我们来看一下Cascades的前身也就是Volcano优化器框架的paper。
397 0
The Volcano Optimizer Generator : Extensibility and Efficient Search
|
SQL 监控 算法
Adaptive Execution of Compiled Queries 论文解读
本篇是TUM的内存数据库HyPer针对compile-based执行框架的改进。其中涉及到HyPer的动态编译和并行执行框架 动态编译文章的结尾提到了编译执行系统存在的2个问题,其中之一就是:不可控的编译时间。
405 0
Adaptive Execution of Compiled Queries 论文解读
|
机器学习/深度学习 人工智能 搜索推荐
Sequential Recommendation with Self-Attentive Multi-Adversarial Network
回顾推荐系统的发展,离不开模型、数据、训练方式三个维度的创新。模型层面,基于内容的推荐系统到协同过滤的矩阵分解,以及神经网络带来的序列化建模,使得用户表征和商品表征刻画越来越精细;数据层面,长短期行为的切分,基于session的推荐[1],跨场景行为的引入,数据的丰富和建模为用户兴趣的挖掘提供更多的可能;训练方式上,分布式训练框架,在线学习,高维稀疏特征处理,优化器设计,从而支撑商业化推荐系统。然而,性能提升的同时,我们也看到推荐系统的可解释性逐渐变成黑盒。用户的兴趣偏好如何表征,兴趣如何演变,不同时间点的历史行为由哪种因素主导,这一系列的问题都对现代化推荐系统的解释提出了挑战。
Sequential Recommendation with Self-Attentive  Multi-Adversarial Network