如何在 Oracle 中重置序列?
Posted
技术标签:
【中文标题】如何在 Oracle 中重置序列?【英文标题】:How do I reset a sequence in Oracle? 【发布时间】:2010-09-08 06:21:45 【问题描述】:在PostgreSQL,我可以这样做:
ALTER SEQUENCE serial RESTART WITH 0;
是否有类似的 Oracle?
【问题讨论】:
看看“序列重置”here。 警告:以下所有代码仅对最初使用“递增 1”创建的序列有效。如果原始序列是使用增量创建的!= 1;应用上述任何程序后,增量将变为 1!可以从 user_sequences 视图中获取要使用的正确增量值。 删除并重新创建序列 【参考方案1】:这是一个从 Oracle 大师 Tom Kyte 将任何序列重置为 0 的好程序。在下面的链接中也对利弊进行了很好的讨论。
tkyte@TKYTE901.US.ORACLE.COM>
create or replace
procedure reset_seq( p_seq_name in varchar2 )
is
l_val number;
begin
execute immediate
'select ' || p_seq_name || '.nextval from dual' INTO l_val;
execute immediate
'alter sequence ' || p_seq_name || ' increment by -' || l_val ||
' minvalue 0';
execute immediate
'select ' || p_seq_name || '.nextval from dual' INTO l_val;
execute immediate
'alter sequence ' || p_seq_name || ' increment by 1 minvalue 0';
end;
/
从此页面:Dynamic SQL to reset sequence value 另一个很好的讨论也在这里:How to reset sequences?
【讨论】:
@Dougman:hi'm 初学者.... p_seq_name || '.nextval INTO l_val from dual' ; @Thiyagu:在 PL/SQL 中,这是使用execute immediate
捕获最多返回 1 行的选择输出时的语法。这是关于立即执行的文档:docs.oracle.com/cd/B28359_01/appdev.111/b28370/…
@matra 我没有看到需要重置序列并与具有相同序列的其他用户处于并发环境中的场景。
为什么需要选择序列,为什么不直接做最后一行'alter sequence ' || p_seq_name || ' increment by 1 minvalue 0';
【参考方案2】:
真正的重启是不可能的AFAIK。 (如果我错了,请纠正我!)。
但是,如果您想将其设置为 0,您可以删除并重新创建它。
如果要设置为特定值,可以将INCREMENT设置为负值,获取下一个值。
也就是说,如果你的序列是500,你可以通过设置它为100
ALTER SEQUENCE serial INCREMENT BY -400;
SELECT serial.NEXTVAL FROM dual;
ALTER SEQUENCE serial INCREMENT BY 1;
【讨论】:
只是给 PLSQL 人员的一个注释。一定要加上“limit 1;”或 "rownum =1" 到 select 语句,否则您最终可能会运行 nextVal 几次并以 -400 为增量增加很多次。 错误序列 .NEXTVAL 低于 MINVALUE 并且在 INCREMENT BY -> 时无法实例化【参考方案3】:alter sequence serial restart start with 1;
此功能在 18c 中正式添加,但自 12.1 起非官方可用。
在 12.1 中使用这个未记录的功能可以说是安全的。尽管语法不包含在official documentation 中,但它是由Oracle 包DBMS_METADATA_DIFF 生成的。我已经在生产系统上多次使用过它。但是,我创建了一个 Oracle 服务请求,他们证实这不是文档错误,该功能确实不受支持。
在 18c 中,该功能不会出现在 SQL 语言语法中,但包含在 Database Administrator's Guide 中。
【讨论】:
嘿@Jon,我知道未记录的功能,但是,我不知道它出现在从 DBMS_METADATA_DIFF 生成的脚本中。你能告诉我你是如何生成脚本的,哪个过程等等?我也会尝试测试它。 @LalitKumarB 我在回答 this question 时偶然发现了这个功能。 啊,现在明白了。谢谢:-) 在序列的最小值大于0的情况下考虑写... RESTART START WITH 0 MINVALUE 0
信息:此功能也适用于 Oracle DB 12.2 (12c)。很好的答案,谢谢!【参考方案4】:
这是我的方法:
-
删除序列
重新创建它
例子:
--Drop sequence
DROP SEQUENCE MY_SEQ;
-- Create sequence
create sequence MY_SEQ
minvalue 1
maxvalue 999999999999999999999
start with 1
increment by 1
cache 20;
【讨论】:
请注意,drop 将使依赖于该序列的任何对象无效,并且必须重新编译它们。 您还必须重新授予从序列中选择的所有授权。【参考方案5】:我的方法是对Dougman's example 进行小范围扩展。
扩展是...
传入种子值作为参数。为什么?我喜欢称将序列重置为某些表中使用的最大 ID。我最终从另一个脚本调用这个过程,该脚本对一大堆序列执行多次调用,将 nextval 重置回某个足够高的级别,在我使用序列的值作为唯一标识符的情况下不会导致主键违规。
它还尊重之前的最小值。如果所需的 p_val 或 现有最小值 高于当前或计算的下一个值,它实际上可能 将下一个值推得更高。 p>
最重要的是,可以调用它来重置为指定值,然后等到最后看到包装器“修复我的所有序列”过程。
create or replace
procedure Reset_Sequence( p_seq_name in varchar2, p_val in number default 0)
is
l_current number := 0;
l_difference number := 0;
l_minvalue user_sequences.min_value%type := 0;
begin
select min_value
into l_minvalue
from user_sequences
where sequence_name = p_seq_name;
execute immediate
'select ' || p_seq_name || '.nextval from dual' INTO l_current;
if p_Val < l_minvalue then
l_difference := l_minvalue - l_current;
else
l_difference := p_Val - l_current;
end if;
if l_difference = 0 then
return;
end if;
execute immediate
'alter sequence ' || p_seq_name || ' increment by ' || l_difference ||
' minvalue ' || l_minvalue;
execute immediate
'select ' || p_seq_name || '.nextval from dual' INTO l_difference;
execute immediate
'alter sequence ' || p_seq_name || ' increment by 1 minvalue ' || l_minvalue;
end Reset_Sequence;
该过程本身很有用,但现在让我们添加另一个调用它并使用序列命名约定以编程方式指定所有内容并查找现有表/字段中使用的最大值...
create or replace
procedure Reset_Sequence_to_Data(
p_TableName varchar2,
p_FieldName varchar2
)
is
l_MaxUsed NUMBER;
BEGIN
execute immediate
'select coalesce(max(' || p_FieldName || '),0) from '|| p_TableName into l_MaxUsed;
Reset_Sequence( p_TableName || '_' || p_Fieldname || '_SEQ', l_MaxUsed );
END Reset_Sequence_to_Data;
现在我们正在用煤气做饭!
上述过程将检查表中字段的最大值,从表/字段对构建序列名称,并使用检测到的最大值调用 "Reset_Sequence"。
这个拼图的最后一块,锦上添花接下来......
create or replace
procedure Reset_All_Sequences
is
BEGIN
Reset_Sequence_to_Data( 'ACTIVITYLOG', 'LOGID' );
Reset_Sequence_to_Data( 'JOBSTATE', 'JOBID' );
Reset_Sequence_to_Data( 'BATCH', 'BATCHID' );
END Reset_All_Sequences;
在我的实际数据库中,大约有一百个其他序列通过这种机制被重置,因此在上述过程中还有 97 次调用 Reset_Sequence_to_Data。
喜欢吗?讨厌它?冷漠?
【讨论】:
我喜欢它。 我会添加一个变量来从 user_sequences 表中按值获取并保存增量。 (可能不是 1)。注意:可能需要改用 all_sequences 表。在这种情况下,您可能还想传入 sequence_owner。 不能给你足够多的支持。当您处理数据迁移时,这是一个非常常见的问题,如果您遇到序列问题,这是 AFAIK 的最佳方法。 赞成,因为这是一个很好的方法。唯一的缺点是它会在 RAC 系统中导致不可预测的行为,其中l_current
可能是各种值之一,具体取决于运行脚本的节点;重新运行脚本可能会导致不同的结果。我发现如果我多次运行它,它最终会确定一个特定的值。【参考方案6】:
以下脚本将序列设置为所需的值:
给定一个新创建的名为 PCS_PROJ_KEY_SEQ 的序列和表 PCS_PROJ:
BEGIN
DECLARE
PROJ_KEY_MAX NUMBER := 0;
PROJ_KEY_CURRVAL NUMBER := 0;
BEGIN
SELECT MAX (PROJ_KEY) INTO PROJ_KEY_MAX FROM PCS_PROJ;
EXECUTE IMMEDIATE 'ALTER SEQUENCE PCS_PROJ_KEY_SEQ INCREMENT BY ' || PROJ_KEY_MAX;
SELECT PCS_PROJ_KEY_SEQ.NEXTVAL INTO PROJ_KEY_CURRVAL FROM DUAL;
EXECUTE IMMEDIATE 'ALTER SEQUENCE PCS_PROJ_KEY_SEQ INCREMENT BY 1';
END;
END;
/
【讨论】:
您忘记了第一个 DDL 语句中的减号(另外,还有一个额外的END
关键字)。【参考方案7】:
这个stored procedure 重新启动我的序列:
Create or Replace Procedure Reset_Sequence
is
SeqNbr Number;
begin
/* Reset Sequence 'seqXRef_RowID' to 0 */
Execute Immediate 'Select seqXRef.nextval from dual ' Into SeqNbr;
Execute Immediate 'Alter sequence seqXRef increment by - ' || TO_CHAR(SeqNbr) ;
Execute Immediate 'Select seqXRef.nextval from dual ' Into SeqNbr;
Execute Immediate 'Alter sequence seqXRef increment by 1';
END;
/
【讨论】:
+1 - 你也可以参数化它以传入序列名称。【参考方案8】:在 Oracle 中还有另一种重置序列的方法:设置 maxvalue
和 cycle
属性。当序列的nextval
命中maxvalue
时,如果设置了cycle
属性,那么它将从序列的minvalue
重新开始。
与设置负数 increment by
相比,此方法的优点是在重置过程运行时可以继续使用序列,从而减少您需要采取某种形式的中断来进行重置的机会。
maxvalue
的值必须大于当前的nextval
,因此下面的过程包含一个可选参数,允许在在过程中选择 nextval
和设置cycle
属性。
create sequence s start with 1 increment by 1;
select s.nextval from dual
connect by level <= 20;
NEXTVAL
----------
1
...
20
create or replace procedure reset_sequence ( i_buffer in pls_integer default 0)
as
maxval pls_integer;
begin
maxval := s.nextval + greatest(i_buffer, 0); --ensure we don't go backwards!
execute immediate 'alter sequence s cycle minvalue 0 maxvalue ' || maxval;
maxval := s.nextval;
execute immediate 'alter sequence s nocycle maxvalue 99999999999999';
end;
/
show errors
exec reset_sequence;
select s.nextval from dual;
NEXTVAL
----------
1
目前的过程仍然允许另一个会话获取值 0,这对您来说可能是也可能不是问题。如果是,您可以随时:
在第一个变更中设置minvalue 1
排除第二个nextval
fetch
将设置nocycle
属性的语句移动到另一个过程中,以便在以后运行(假设您要这样做)。
【讨论】:
【参考方案9】:1) 假设您创建了一个如下所示的序列:
CREATE SEQUENCE TESTSEQ
INCREMENT BY 1
MINVALUE 1
MAXVALUE 500
NOCACHE
NOCYCLE
NOORDER
2) 现在您从 SEQUENCE 中获取值。假设我已经提取了四次,如下所示。
SELECT TESTSEQ.NEXTVAL FROM dual
SELECT TESTSEQ.NEXTVAL FROM dual
SELECT TESTSEQ.NEXTVAL FROM dual
SELECT TESTSEQ.NEXTVAL FROM dual
3) 执行以上四个命令后,SEQUENCE 的值为 4。现在假设我再次将 SEQUENCE 的值重置为 1。遵循以下步骤。按照如下所示的相同顺序执行所有步骤:
ALTER SEQUENCE TESTSEQ INCREMENT BY -3;
SELECT TESTSEQ.NEXTVAL FROM dual
ALTER SEQUENCE TESTSEQ INCREMENT BY 1;
SELECT TESTSEQ.NEXTVAL FROM dual
【讨论】:
【参考方案10】:Jezus,所有这些编程都只是为了重新启动索引... 也许我是个白痴,但是对于 pre-oracle 12(具有重启功能),simpel 有什么问题:
drop sequence blah;
create sequence blah
?
【讨论】:
删除序列的主要问题是它失去了授予的权限。 好吧,乔恩。大多数情况下,恢复这些将比所有编程花费更少的时间。好的 DBA 通常有脚本,所以这不应该是一个问题 :-)【参考方案11】:更改序列的 INCREMENT 值、递增它,然后再将其更改回来非常轻松,而且您还有一个额外的好处,那就是不必像删除/重新创建序列那样重新建立所有授权。
【讨论】:
【参考方案12】:您可以使用 CYCLE 选项,如下所示:
CREATE SEQUENCE test_seq
MINVALUE 0
MAXVALUE 100
START WITH 0
INCREMENT BY 1
CYCLE;
这种情况下,当序列达到MAXVALUE(100)时,会循环到MINVALUE(0)。
在递减序列的情况下,序列将循环到 MAXVALUE。
【讨论】:
对于反对者(他们永远不会看到此评论):CYCLE 属性正是我用来完成序列重置的。重置是自动的这一事实并不意味着它没有完成目标——OP 没有指定重置必须针对预先存在的序列!【参考方案13】:我创建一个块来重置我的所有序列:
DECLARE
I_val number;
BEGIN
FOR US IN
(SELECT US.SEQUENCE_NAME FROM USER_SEQUENCES US)
LOOP
execute immediate 'select ' || US.SEQUENCE_NAME || '.nextval from dual' INTO l_val;
execute immediate 'alter sequence ' || US.SEQUENCE_NAME || ' increment by -' || l_val || ' minvalue 0';
execute immediate 'select ' || US.SEQUENCE_NAME || '.nextval from dual' INTO l_val;
execute immediate 'alter sequence ' || US.SEQUENCE_NAME || ' increment by 1 minvalue 0';
END LOOP;
END;
【讨论】:
【参考方案14】:这是一个更强大的过程,用于更改序列返回的下一个值,还有更多。
首先,它可以防止 SQL 注入攻击,因为传入的字符串都不用于直接创建任何动态 SQL 语句, 其次,它防止下一个序列值设置在最小或最大序列值的范围之外。next_value
将是 != min_value
并介于 min_value
和 max_value
之间。
第三,它会在清理时考虑当前(或建议的)increment_by
设置以及所有其他序列设置。
除第一个之外的所有参数都是可选的,除非指定,否则将当前序列设置作为默认值。如果未指定可选参数,则不执行任何操作。
最后,如果您尝试更改不存在(或不属于当前用户)的序列,则会引发 ORA-01403: no data found
错误。
代码如下:
CREATE OR REPLACE PROCEDURE alter_sequence(
seq_name user_sequences.sequence_name%TYPE
, next_value user_sequences.last_number%TYPE := null
, increment_by user_sequences.increment_by%TYPE := null
, min_value user_sequences.min_value%TYPE := null
, max_value user_sequences.max_value%TYPE := null
, cycle_flag user_sequences.cycle_flag%TYPE := null
, cache_size user_sequences.cache_size%TYPE := null
, order_flag user_sequences.order_flag%TYPE := null)
AUTHID CURRENT_USER
AS
l_seq user_sequences%rowtype;
l_old_cache user_sequences.cache_size%TYPE;
l_next user_sequences.min_value%TYPE;
BEGIN
-- Get current sequence settings as defaults
SELECT * INTO l_seq FROM user_sequences WHERE sequence_name = seq_name;
-- Update target settings
l_old_cache := l_seq.cache_size;
l_seq.increment_by := nvl(increment_by, l_seq.increment_by);
l_seq.min_value := nvl(min_value, l_seq.min_value);
l_seq.max_value := nvl(max_value, l_seq.max_value);
l_seq.cycle_flag := nvl(cycle_flag, l_seq.cycle_flag);
l_seq.cache_size := nvl(cache_size, l_seq.cache_size);
l_seq.order_flag := nvl(order_flag, l_seq.order_flag);
IF next_value is NOT NULL THEN
-- Determine next value without exceeding limits
l_next := LEAST(GREATEST(next_value, l_seq.min_value+1),l_seq.max_value);
-- Grab the actual latest seq number
EXECUTE IMMEDIATE
'ALTER SEQUENCE '||l_seq.sequence_name
|| ' INCREMENT BY 1'
|| ' MINVALUE '||least(l_seq.min_value,l_seq.last_number-l_old_cache)
|| ' MAXVALUE '||greatest(l_seq.max_value,l_seq.last_number)
|| ' NOCACHE'
|| ' ORDER';
EXECUTE IMMEDIATE
'SELECT '||l_seq.sequence_name||'.NEXTVAL FROM DUAL'
INTO l_seq.last_number;
l_next := l_next-l_seq.last_number-1;
-- Reset the sequence number
IF l_next <> 0 THEN
EXECUTE IMMEDIATE
'ALTER SEQUENCE '||l_seq.sequence_name
|| ' INCREMENT BY '||l_next
|| ' MINVALUE '||least(l_seq.min_value,l_seq.last_number)
|| ' MAXVALUE '||greatest(l_seq.max_value,l_seq.last_number)
|| ' NOCACHE'
|| ' ORDER';
EXECUTE IMMEDIATE
'SELECT '||l_seq.sequence_name||'.NEXTVAL FROM DUAL'
INTO l_next;
END IF;
END IF;
-- Prepare Sequence for next use.
IF COALESCE( cycle_flag
, next_value
, increment_by
, min_value
, max_value
, cache_size
, order_flag) IS NOT NULL
THEN
EXECUTE IMMEDIATE
'ALTER SEQUENCE '||l_seq.sequence_name
|| ' INCREMENT BY '||l_seq.increment_by
|| ' MINVALUE '||l_seq.min_value
|| ' MAXVALUE '||l_seq.max_value
|| CASE l_seq.cycle_flag
WHEN 'Y' THEN ' CYCLE' ELSE ' NOCYCLE' END
|| CASE l_seq.cache_size
WHEN 0 THEN ' NOCACHE'
ELSE ' CACHE '||l_seq.cache_size END
|| CASE l_seq.order_flag
WHEN 'Y' THEN ' ORDER' ELSE ' NOORDER' END;
END IF;
END;
【讨论】:
【参考方案15】:在我的项目中,曾经有人在不使用序列的情况下手动输入记录,因此我必须手动重置序列值,为此我在下面写了sql代码sn-p:
declare
max_db_value number(10,0);
cur_seq_value number(10,0);
counter number(10,0);
difference number(10,0);
dummy_number number(10);
begin
-- enter table name here
select max(id) into max_db_value from persons;
-- enter sequence name here
select last_number into cur_seq_value from user_sequences where sequence_name = 'SEQ_PERSONS';
difference := max_db_value - cur_seq_value;
for counter in 1..difference
loop
-- change sequence name here as well
select SEQ_PERSONS.nextval into dummy_number from dual;
end loop;
end;
请注意,如果序列滞后,上述代码将起作用。
【讨论】:
【参考方案16】:以下是如何使所有自增序列匹配实际数据:
创建一个过程来强制执行下一个值,正如该线程中已经描述的那样:
CREATE OR REPLACE PROCEDURE Reset_Sequence(
P_Seq_Name IN VARCHAR2,
P_Val IN NUMBER DEFAULT 0)
IS
L_Current NUMBER := 0;
L_Difference NUMBER := 0;
L_Minvalue User_Sequences.Min_Value%Type := 0;
BEGIN
SELECT Min_Value
INTO L_Minvalue
FROM User_Sequences
WHERE Sequence_Name = P_Seq_Name;
EXECUTE Immediate 'select ' || P_Seq_Name || '.nextval from dual' INTO L_Current;
IF P_Val < L_Minvalue THEN
L_Difference := L_Minvalue - L_Current;
ELSE
L_Difference := P_Val - L_Current;
END IF;
IF L_Difference = 0 THEN
RETURN;
END IF;
EXECUTE Immediate 'alter sequence ' || P_Seq_Name || ' increment by ' || L_Difference || ' minvalue ' || L_Minvalue;
EXECUTE Immediate 'select ' || P_Seq_Name || '.nextval from dual' INTO L_Difference;
EXECUTE Immediate 'alter sequence ' || P_Seq_Name || ' increment by 1 minvalue ' || L_Minvalue;
END Reset_Sequence;
创建另一个过程来协调所有序列与实际内容:
CREATE OR REPLACE PROCEDURE RESET_USER_SEQUENCES_TO_DATA
IS
STMT CLOB;
BEGIN
SELECT 'select ''BEGIN'' || chr(10) || x || chr(10) || ''END;'' FROM (select listagg(x, chr(10)) within group (order by null) x FROM ('
|| X
|| '))'
INTO STMT
FROM
(SELECT LISTAGG(X, ' union ') WITHIN GROUP (
ORDER BY NULL) X
FROM
(SELECT CHR(10)
|| 'select ''Reset_Sequence('''''
|| SEQ_NAME
|| ''''','' || coalesce(max('
|| COL_NAME
|| '), 0) || '');'' x from '
|| TABLE_NAME X
FROM
(SELECT TABLE_NAME,
REGEXP_SUBSTR(WTEXT, 'NEW\.(\S*) IS NULL',1,1,'i',1) COL_NAME,
REGEXP_SUBSTR(BTEXT, '(\.|\s)([a-z_]*)\.nextval',1,1,'i',2) SEQ_NAME
FROM USER_TRIGGERS
LEFT JOIN
(SELECT NAME BNAME,
TEXT BTEXT
FROM USER_SOURCE
WHERE TYPE = 'TRIGGER'
AND UPPER(TEXT) LIKE '%NEXTVAL%'
)
ON BNAME = TRIGGER_NAME
LEFT JOIN
(SELECT NAME WNAME,
TEXT WTEXT
FROM USER_SOURCE
WHERE TYPE = 'TRIGGER'
AND UPPER(TEXT) LIKE '%IS NULL%'
)
ON WNAME = TRIGGER_NAME
WHERE TRIGGER_TYPE = 'BEFORE EACH ROW'
AND TRIGGERING_EVENT = 'INSERT'
)
)
) ;
EXECUTE IMMEDIATE STMT INTO STMT;
--dbms_output.put_line(stmt);
EXECUTE IMMEDIATE STMT;
END RESET_USER_SEQUENCES_TO_DATA;
注意事项:
-
程序从触发器代码中提取名称,不依赖于命名约定
要在执行前检查生成的代码,请在最后两行切换 cmets
【讨论】:
【参考方案17】:我做了一个替代方案,用户不需要知道值,系统获取并使用变量来更新。
--Atualizando sequence da tabela SIGA_TRANSACAO, pois está desatualizada
DECLARE
actual_sequence_number INTEGER;
max_number_from_table INTEGER;
difference INTEGER;
BEGIN
SELECT [nome_da_sequence].nextval INTO actual_sequence_number FROM DUAL;
SELECT MAX([nome_da_coluna]) INTO max_number_from_table FROM [nome_da_tabela];
SELECT (max_number_from_table-actual_sequence_number) INTO difference FROM DUAL;
IF difference > 0 then
EXECUTE IMMEDIATE CONCAT('alter sequence [nome_da_sequence] increment by ', difference);
--aqui ele puxa o próximo valor usando o incremento necessário
SELECT [nome_da_sequence].nextval INTO actual_sequence_number from dual;
--aqui volta o incremento para 1, para que futuras inserções funcionem normalmente
EXECUTE IMMEDIATE 'ALTER SEQUENCE [nome_da_sequence] INCREMENT by 1';
DBMS_OUTPUT.put_line ('A sequence [nome_da_sequence] foi atualizada.');
ELSE
DBMS_OUTPUT.put_line ('A sequence [nome_da_sequence] NÃO foi atualizada, já estava OK!');
END IF;
END;
【讨论】:
【参考方案18】:对我有用的存储过程
create or replace
procedure reset_sequence( p_seq_name in varchar2, tablename in varchar2 )
is
l_val number;
maxvalueid number;
begin
execute immediate 'select ' || p_seq_name || '.nextval from dual' INTO l_val;
execute immediate 'select max(id) from ' || tablename INTO maxvalueid;
execute immediate 'alter sequence ' || p_seq_name || ' increment by -' || l_val || ' minvalue 0';
execute immediate 'select ' || p_seq_name || '.nextval from dual' INTO l_val;
execute immediate 'alter sequence ' || p_seq_name || ' increment by '|| maxvalueid ||' minvalue 0';
execute immediate 'select ' || p_seq_name || '.nextval from dual' INTO l_val;
execute immediate 'alter sequence ' || p_seq_name || ' increment by 1 minvalue 0';
end;
如何使用存储过程:
execute reset_sequence('company_sequence','company');
【讨论】:
以上是关于如何在 Oracle 中重置序列?的主要内容,如果未能解决你的问题,请参考以下文章