作者:empty 出版社:empty |
ORACLE10G数据库开发
优化指南
目录
目录2
1:绑定变量3
2:不要频繁提交,不要使用自动提交5
3:索引7
什么情况下适合建立索引:7
如何使优化器使用索引:8
4:高效SQL10
查询中使用索引10
表联结查询11
5:常见的、流传很广的传言13
6:临时表的使用15
7:autotrace16
8:收集统计信息17
1:绑定变量
绑定变量是一个数据库开发人员所应该具备的最基本的习惯之一。
但是在现实中,很多开发人员完全不使用绑定变量,甚至根本不知道绑定变量是何物。这个很大程度上是因为他们不了解使用或者不使用绑定变量对数据库造成的影响。如果他们知道了,我敢肯定他们一定会很吃惊。
任何一句提交给ORACLE数据库运行的语句,在正式运行前,都需要通过数据库引擎的解析。解析分为2种,一种是硬解析,一种是软解析。为了理解方便,简单的说,如果你使用了绑定变量,那么数据库会倾向于使用软解析,如果你没有使用绑定变量,那么数据库会倾向于使用硬解析。
从字面上理解,硬解析使用的资源会比软解析多。但是到底有多少?很难在这个文章里面进行全面描写,但是可以从其中的一个方面进行理解:在一个SQL语句的硬解析中,会产生数百个乃至数千个LATCH,而这些LATCH锁住的都是数据库必须在实例级别串行访问的资源,并且请注意,这么多的LATCH是硬解析一句SQL语句中产生的!软解析则不需要产生这些LATCH。单从这一点就可以知道,硬解析对数据库而言是一个极其昂贵的操作。如果开发人员在一个循环里面调用了一个SQL语句而又没有绑定变量,那么可以想象对数据库会造成多大的压力!
****************************************************************************
LATCH不等于锁LOCK,它是一种轻量级的LOCK,用来保证对资源的串行访问。对于被LOCK住资源的申请,如果不能满足,那么申请者会被阻塞以后进入一个等待队列等待,当资源被释放的时候,按照先到先得的顺序唤醒队列里面的第一个等待者。而对被LATCH住的资源的申请,如果不能满足,那么申请的进程不会被阻塞也不会等待,它只会在很短的一个时间以后再次重新申请。申请LATCH住资源的进程能否得到这个资源全凭运气:它们不会排队等待,而是随机的去碰运气—不停的尝试申请。
****************************************************************************
CURSOR_SHAREING参数是ORACLE提供的一个对于不使用绑定变量的解决方案,它如果设置成FORCE,可以在数据库实例上对所有提交给数据库的SQL语句强制进行绑定变量。这看来好象是对开发人员的一种解放,但是,请注意下面2点:
1:CURSOR_SHAREING会引入它自己的额外开销。
2:它有可能会引发一些与手动绑定变量不同的、不可意料的SQL执行返回结果,这增加了测试和排错的难度。
CURSOR_SHAREING绝对不是解决绑定变量的万能方法,相反,它这是ORACLE提供的一种不得已而为之的解决方案,只有当运行系统因为硬解析而不堪重负,而短期又不可能全面重写代码的情况下使用的,而且使用以前一定要经过充分严格的测试。
当然,过度绑定变量是有可能导致某个查询性能下降的,实际中也会有这样的场景。但是,相比较而言,不使用绑定变量而带来的性能问题要更普遍和严重得多。
以JAVA为例,所有提交给数据库执行的SQL语句应该如下面这样绑定变量。
{
Connection con = DriverManager.getConnection
( jdbc:oracle:oci:@database , scott , tiger );
conn.setAutoCommit(false);//把自动提交修改为非自动提交。
String v_sql = 'select name from table_a where id = ? '; //嵌入绑定变量
ReparedStatement stmt = conn.prepareStatement( v_sql );
stmt.setString(1, 'xxxx' ); //为绑定变量赋值
stmt.executeQuery();
conn.commit();
}
所以,请开发人员特别注意:除非有充分的理由,否则,请确保你提交给数据库执行的任何一句SQL语句都是手工绑定了变量的。
2:不要频繁提交,不要使用自动提交
要根据业务逻辑的需求,保证业务数据一致性的情况下提交。
实际上,开发人员使用频繁提交、自动提交主要是他们的认识里面有下面2条
1:在做删除和修改的过程中,对数据行的锁定是稀有资源,而且会阻塞对数据库相应行的读。
2:ODBC,JDBC模型中事务默认都是自动提交(隐式提交)。
对于第一点,不能说是错误的,因为在其他的主流数据库(DB2,SYBASE,SQLSERVER)中,这个概念是正确的。但是在ORACLE中,完全不是这么回事情。ORACLE的并发模型以及对于行锁的实现方法使得它对10000行数据的写锁定所消耗的资源与对1行数据的写锁定所消耗的资源是完全相同的--它们实际上都不消耗任何额外资源。而且,任何的写锁定都不会阻塞其他会话对该数据的读取。
对于第2点,开发人员应该手动把它修改成非自动提交。在ODBC中默认设置是自动提交是很好理解的,因为ODBC是为SQLSERVER的开发人员设计的,SQLSERVER的并发模型使得锁定是一种非常稀有的资源,必须尽快提交并且释放锁定。但是这个特性不应该被继承到JDBC中。JDBC被用来设计支持企业级的应用,而事务则是企业级应用的核心特征,自动提交几乎肯定会破坏事务的原子性。
以JAVA为例,我认为任何与数据库事务相关的代码都应该是这样的:
{
Connection con = DriverManager.getConnection
( jdbc:oracle:oci:@database , scott , tiger );
conn.setAutoCommit(false);//把自动提交修改为非自动提交。
//业务代码
//业务代码
//业务代码
conn.commit();//根据业务需求最后再一并提交。
}
如果不把自动提交修改成非自动提交,那么代码是极其糟糕的。
实际上,频繁的递交还会大大加强ORACLE数据库的压力,导致性能下降。这是因为ORACLE的多版本控制和行级锁定的特征使得它提交1行数据所产生的负荷与提交10000行数据所产生的负荷几乎相等。很显然,对比下面的这两段代码:
代码1(糟糕,如果使用自动提交,那么即使没有代码中的这2句commit,也等效与下面的代码):
for (int i=0;i 10000;i++)
{
delete from table1 where ....
commit;
insert into table2 where ....
commit;
}
代码2(优良):
for (int i=0;i 10000;i++)
{
delete from table1 where ....
insert into table2 where ....
}
commit;
就算不考虑代码1的频繁commit已经破坏了事务的原子性,与业务初衷相背,单从性能来说,代码2已经高出代码1很多倍了。
我曾经不止一次的在网上看到所谓ORACLE优化的文章,里面说:commit越频繁越好,越能加快释放资源,增加性能。这样的文章实际上是不对的。在别的主流数据库中,这个说法大多正确,但是在ORACLE不是这样的!
有必要再重复总结一次,在基于ORACLE的数据库开发中:
1:不要有意缩短事务的大小,而应该根据业务逻辑合理安排事务,业务需要多大的事务就安排多大的事务,业务逻辑需要事务什么时候提交才提交。
2:在ORACLE数据库中,行级锁不是一种稀缺资源,不会造成数据库性能低下,行级锁不会消耗数据库的任何额外资源。
3:索引
索引是数据库永恒而重要的话题。
ORACLE中主要有下面类型的索引:
B*树索引,位图索引,位图联结索引,基于函数索引,应用域索引。
而我们平时所说的索引指的是B*树索引,这是数据库最常用的一类索引结构。
但是,索引不是提高查询效率的万能灵药。我经常听到开发人员这样评价: 索引肯定能加快查询。 多加几个索引不要紧,反正它不会让我们的系统更慢 。实际上,这样的话是绝对错误的。
请开发人员记住,你增加的任何一个索引都有可能对你的数据库查询产生下面的影响:
1:更快。
2:不快也不慢,速度不变。
3:更糟糕了,更慢了。
在我所见过的所有生产系统中,我都见过大量的能让数据库更慢、负担更重的索引。我曾经还在一个15个字段的表中见到了14个索引,唯一没有被索引的字段是那个长长的Description字段。而实际上,这14个索引中,有10个大概是永远不会被用到的。而那可能被用到的4个索引也可以通过1到2个组合索引来代替。
无用索引使系统变慢的原因:
1:数据库加载大量无用索引到内存,并在运行中管理它们会消耗大量的资源。
2:系统的增、删、改操作会因为需要同步大量无用的索引而变得很慢。
所以,开发人员在建立任何一个索引以前都需要认真考虑:这个索引是必须的吗?它能被数据库使用吗?如果回答是坚定的肯定,那么这个索引就是值得建立的。否则,请不要建。
什么情况下适合建立索引:
1:搜索列有比较高的选择性
比如说有一个员工登记表,它有2个字段(name,gender,age).如果有100条记录,那么我们知道,在name字段上,将有100个(或者非常接近100)不同的值,那么我们就说这个字段选择性是很高的,在这个字段上面的索引将很有可能在查询中被优化器选中。而在gender字段上,100条记录最多只能有2个不同的值,这样我们就所这个字段选择性是很低的,在这个字段上的索引几乎不会被优化器选中。
现在,在这2个字段上面分别建立索引index1 on name,index2 on gender.
如果有一个查询是根据name进行搜索,那么查询优化器会偏向于使用index1.如果有一个查询是基于gender,那么可以肯定的说,查询优化器几乎不会去使用index2,换一句话说,index2几乎永远不会不被使用-它的存在是没有意义的,只能对我们的系统产生负面影响。
2:索引能单独回答SQL查询。
在这样的情况下,索引做为一个比较“瘦”版本而且是排序过的表,对查询性能是有很大帮助的。还是用上面的员工登记表来说明情况,如果现在有一个查询是基于用户的name和gender,而且查询出来的字段也只包括name和gender,(select name,gender from table where gender='XXX' and name='XXX')那么,单独建立在name上的索引和单独建立在gendar上面的索引都不能回答。这个时候,如果有一个索引是建立在name和gender上面的, 比如index3 on (name,gender)或者index3 on (gender,name),那么,数据库单独访问索引就能提供所有的信息,于是,数据库就会偏向于使用这个索引。实际上,在上面的这个例子中,可以肯定的说,在任何情况下数据库都几乎一定会使用这个索引。
还有一点,建立索引的时候应该适当考虑组合索引,既几个字段组成一个索引。
如何使优化器使用索引:
不要在索引列上引入任何计算,包括函数。
select * from table1 where col1 + 2= 10;
这样的语句,即使col1上面有合适的索引,优化器也肯定不会使用它。
而如果使用下面完全等价的写法:
select * from table1 where col1 = 8;
那么,优化器会偏向于使用合适的索引。
基于函数的索引
ORACLE现在已经发展了一种叫做“基于函数的索引”(function-based index)的技术。这个技术是直接对计算得出的列建立索引,并在查询中使用。
例如可以如下面一样建立一个函数索引:
create index table1_idx1 on table1 (upper(col1));
这样,下面的查询就会倾向于使用索引。
select * from table1 where upper(col1)='ABC';
搜索中请尽量包含组合索引的前导列,这样在搜索中,索引将会最大限度的被正确使用。
如果在表table1 (col1,col2,col3,col4)上有一个对前3个字段的组合索引index1 on (col1,col2,col3)那么只有在搜索中where语句后面引用了col1,那索引才肯定会被正确的使用。
会正确使用索引的情况:
select * from table1 where col1='xxx' and col2='xxxx';
select * from table1 where col1='xxx' and col3='xxxx';
在上面的情况中,如果优化器认为使用索引会降低COST,那么它一定会使用索引。
很有可能不会正确使用索引的情况:
select * from table1 where col3='xxx';
select * from table1 where col2='xxx';
上面的情况中,有的时候优化器可能正确的使用索引;但是有的时候并不会使用索引,即使使用索引是一种比较廉价的解决方案。
使用复合索引且第一个索引字段没有出现在where 中时,建议使用hint 强制
如果在搜索条件中有“ is null ”这样的条件,将肯定不会使用索引。
select * from table1 where col1 is null;
这个查询肯定不会使用索引。原因是ORACLE根本不会为字段为NULL值的记录编制索引,也就是说,你必须去做表扫描才能得到相关记录信息。
搜索中有“ != ” 的肯定不会使用索引
select * from table1 where col1 != XXX ;
这个查询肯定不会使用索引,无论使用索引是不是更加廉价,索引都不会在优化器的考虑范围以内。
对于 “!=” 的查询,
比如 select * from table1 where col1 != 10000;
可以考虑使用 col1 > 9999 and col1 10001 的查询来代替。这样的情况下,如果优化器认为使用索引的搜索方案比较廉价,那么索引就会被使用。
比如 select * from table1 where col1 > 9999 and col1 10001;
4:高效SQL
查询中使用索引
避免使用NOT IN,尽量使用NOT EXISTS
1:NOT IN将偏向于不使用索引。而NOT EXISTS可以正确的使用索引。
下面2种情况下都将使用索引,它们执行的COST几乎相同。
SELECT * from EMP where deptno not in ( select deptno from test2 where deptno=10);
SELECT *
FROM EMP D
WHERE not EXISTS (SELECT null
FROM test2 E
WHERE E.DEPTNO = D.DEPTNO and e.deptno=10);
但是在下面的情况下:
Select * from EMP where deptno not in ( select deptno from test2);
SELECT *
FROM EMP D
WHERE not EXISTS (SELECT null
FROM test2 E
WHERE E.DEPTNO = D.DEPTNO );
使用NOT IN的就无法使用索引,而使用NOT EXISTS的可以使用索引,当后面的一个表比较大的时候,它们的执行效率相差非常大。
查询返回尽量少的字段
SELECT 后面不连接*
比如 SELECT * FROM TABLE1
这样会把表TABLE1中的所有字段全部返回,而实际上应该只把需要的字段放在SELECT 后面。比如
SELECT COL1,COL2 FROM TABLE2.这样就会查询需要的2个字段并返回,而不会把所有的字段都查询出来。
EXISTS替换DISTINCT
下面2个SQL是逻辑等效,但是性能相差很大。因为数据库做distinct操作的时候,是先最整个数据集进行SORT排序,然后再遍历数据集,遇到重复的记录就去除。
例子:
例如:
低效
select distinct dept_no, dept_name
from dept d, emp e
where d.dept_no = e.dept_no
高效
select dept_no, dept_name
from dept d
where exists (select null
from emp e
where e.dept_no = d.dept_no);
用UNION ALL替换OR (适用于索引列)
通常情况下, 用UNION ALL替换WHERE子句中的OR将会起到较好的效果. 对索引列使用OR将造成全表扫描. 注意, 以上规则只针对多个索引列有效. 如果有column没有被索引, 查询效率可能会因为你没有选择OR而降低. 当然,UNION ALL有可能输出重复的行数据,所以这个需要开发人员具体问题具体对待了。
在下面的例子中, LOC_ID 和REGION上都建有索引.
高效:
SELECT LOC_ID , LOC_DESC , REGION
FROM LOCATION
WHERE LOC_ID = 10
UNION ALL
SELECT LOC_ID , LOC_DESC , REGION
FROM LOCATION
WHERE REGION = “MELBOURNE”
低效:
SELECT LOC_ID , LOC_DESC , REGION
FROM LOCATION
WHERE LOC_ID = 10 OR REGION = “MELBOURNE”
like 子句尽量前端匹配
like 参数使用得非常频繁,因此如果能够对于like 子句使用索引,将很好地提高查询的
效率。
例如:查询城市代码(city表上有基于city_name列的索引)
select city_name_chinese from city where city_name like ‘%ZHEN%’
优化器偏向于不使用索引。
如果对上面的语句进行修改,成下面的表达形式
select city_name_chinese from city where city_name like ‘SHNEZHEN%’
那么优化器偏向于使用索引。
表联结查询
在ORACLE的RBO以及早期的CBO时代,对于表连接查询的书写规则,有很多技巧。不同的书写有可能产生相差十分巨大的查询路径。
到现在,虽然CBO已经发展非常成熟,可以对很多SQL语句进行对于转换和重写,但是它对很多不优良的SQL语句仍然无法象人一样进行有效的优化。
表联结查询是及其昂贵的操作,尤其是随着表数目的增加,COST增加更快。
一般情况下,一个表联结查询不应该连接超过4个以上的表(注意是表,不是视图),否则容易使查询的性能急剧下降。联结的表数目太多,说明有过多业务逻辑被放到了数据层,这个时候开发人员应该好好考虑是否应该一次性把如此多工作交给数据库查询引擎去做。
1:在表联结中,同一个表应该只出现一次。
如果在一个表联结查询中同一个表出现多次,那么ORACLE就会对这个表扫描多次。很显然,扫描多次是没有必要的,因为扫描一次就可以把这个查询需要的表信息全部读取出来。优化器目前还没有聪明到可以自己把这个联结重写成只需要扫描一次的表联结查询语句。
比如,下面的语句
select t1.col1,t2.col2 from
(select a.col1,b.col2 from table1 a,table2 b where ......) t1,
(select a.col1,c.col2 from table1 a,table3 c where ......) t2 where ....
整个查询联结了3张表,但是对于表table1而言,数据库查询了2次。开发人员一般这样写是出于对业务的理解以及查询的可读性:第1次对table1和table2的联结代表了一个业务上的意义,而第2次对table1和table3的联结又代表了另一个业务上的意义。最后把这2个中间数据集合联接起来又是一个业务上的意义。
如果对上面的语句做下面的更改,可以得到等效的查询语句:
select a.col1,b.col2 from table1 a,table2 b,table3 c where .......
这样数据库对table1,table2,table3分别只进行了一次读取,显然会更快。
尽量消除排序操作
如果可以使用UNION ALL,那么就应该尽量不使用UNION操作。因为UNION操作会把连接的2张表分别进行排序以后将重复的记录剔除然后输出,而UNION ALL就不会排序,也不会把重复记录消除。排序操作是非常昂贵的,尤其对于大表。我曾经见过一个SQL语句,原本是使用UNION连接2个数据集,这个查询大概需要4秒钟。我仔细查看了UNION连接的2个数据集,发现从数据集过滤的条件上判断它们根本不可能有重复的行集,于是我把UNION修改成了UNION ALL,于是这个查询时间缩短到了1秒以内。
5:常见的、流传很广的传言
这些传言并不是错误的。在某些其他的数据库上,以及在一些ORACLE9I以及更早的版本上,它们是正确的。但是在现在的ORACLE10G上,它们已经不再正确或者过时了。尽管严格按照这些“传言”来写SQL并不会对数据库带来任何负面的影响,但是我认为还是很有比较把它们交代说明一下。
传言:在表连接当中,选择小表做为驱动表可以大大提高效率。
表 TAB1 大表,上万条 条记录
表 TAB2 小表 几条 条记录
选择TAB2作为基础表 (最好的方法)
select count(*) from tab1,tab2 执行时间很短。
选择TAB2作为基础表 (不佳的方法)
select count(*) from tab2,tab1 执行时间很长。
实际情况:这个传言在ORACLE的RBO中是正确的。但是在ORACLE10G中,已经只支持CBO。优化器拿到提交上来的SQL语句以后,会对SQL语句进行分析,并自动使用一个合适的驱动表来进行连接。换句话说,无论你书写的顺序如何,优化器都会足够“聪明”地选择一个小表来做驱动表。
传言:过滤条件要后置,以提前过滤,那些可以过滤掉最大数量记录的条件必须写在WHERE子句的末尾
例如:
(曾经认为是低效)
SELECT *
FROM EMP E
WHERE SAL > 50000
AND JOB = 'MANAGER'
AND 25 (SELECT COUNT(*) FROM EMP WHERE MGR=E.EMPNO);
(曾经认为是高效)
SELECT *
FROM EMP E
WHERE 25 (SELECT COUNT(*) FROM EMP WHERE MGR=E.EMPNO)
AND SAL > 50000
AND JOB = 'MANAGER';
实际情况:这个传言在ORACLE的RBO中是正确的。但是在ORACLE10G中,已经只支持CBO。优化器拿到提交上来的SQL语句以后,会对SQL语句进行分析、转换和重写,并且把对表的过滤条件都提前到第一次读取这个表数据的时候。换句话说,无论你书写的顺序如何,优化器都会尽早过滤掉尽可能多的数据。
上面的2中写法,在ORACLE10G中,执行时计划和执行时间都是完全相同的。
传言:对于使用IN和EXISTS查询2张表,外大内小用IN,外小内大用EXISTS,这样能提高查询效率。
实际上:2张表test2(3712条记录),EMP(14条记录)
使用IN
Select deptno from EMP where deptno in ( select deptno from test2 );
使用EXISTS
SELECT d.DEPTNO FROM EMP D WHERE
EXISTS (SELECT null FROM test2 E WHERE E.DEPTNO = D.DEPTNO);
ORACLE10G执行上面2个查询的执行计划、读取数据块数、返回记录、执行时间都完全一样。
而把大表和小表的位置倒过来:
使用IN
Select deptno from test2 where deptno in ( select deptno from EMP );
使用EXISTS
SELECT d.DEPTNO FROM test2 D WHERE
EXISTS (SELECT null FROM EMP E WHERE E.DEPTNO = D.DEPTNO);
ORACLE10G执行上面2个查询的执行计划、读取数据块数、返回记录、执行时间也都是完全一样的。
6:临时表的使用
很多场景下开发人员需要临时表来存储一些中间数据,而开发人员普遍的做法是下面2种之一:
1:在代码中加入DDL语句创建一张表,使用结束以后DROP掉。
2:创建一个全局的表,往里面插入临时数据,使用完成以后把这些数据删除。为了与并发的其他使用这个临时表的会话作到数据隔离,还需要在这个表上填入一些会话ID信息以与其他用户区别。
实际上,这样做完全没有必要。因为ORACLE已经提供了真正的临时表。
ORACLE的临时表是“静态”定义的。每个数据库只创建一次临时表,而不是为数据库中的每一个存储过程都创建一次。
临时表中可以是基于会话的(临时表中的数据可以跨提交存在,即提交后仍然存在,但是断开连接以后在连接时数据就没有了),如下表
SQL>create global temporary table temp_table_session
2 on commit preserve rows
3 as select * from scott.emp
4 /
Table created.
也可以是基于提交的(提交以后数据自动消失),如下表
SQL>create global temporary table temp_table_session
2 on commit delete rows
3 as select * from scott.emp
4 /
Table created.
在任何情况下,一个会话中的临时表数据都不会被其他任何会话看到,因为ORACLE已经对不同会话做了数据隔离。
请注意:临时表不能做为外键的目标,也不能在临时表中定义外键。除此以外,临时表在使用中与永久表没有太大差别。
7:autotrace
autotrace是ORACLE提供的一个工具,可以显示所执行的SQL语句的查询计划(也叫解释计划,explain plan)以及所用的资源。
配置autotrace的方法:
1:cd [oracle_home]/rdbms/admin;
2:做为system登录oracle;
3:运行@utlxplan;
4:运行 grant all on plan_table to public //让所有人都可以访问这个表。
5:cd [oracle_home]/sqlplus/admin
6:做为sys登录oracle
7:运行@plustrce
8:运行grant plustrace to public。
上面就把准备工作全部做好了。
在运行一个SQL语句以前,可以做如下设置,以便打开autotrace跟踪开关
set autotrace explain 只显示SQL语句优化执行路径。
set autotrace statistics只显示SQL语句的执行统计信息。
set autotrace on 既显示SQL语句优化执行路径,又显示SQL语句的执行统计信息。
set autotrace traceonly 不真正执行SQL语句,只显示SQL语句优化执行路径和执行统计信息。
8:收集统计信息
优化器要选择正确的、最廉价的执行路径,那么它必须对数据库内的数据信息的有一个精确的了解,这些信息包括(但不限于):表的记录数、索引状态、记录值的分布情况、记录的物理分布情况。不同的统计信息很有可能让优化器作出完全不同的选择。
ORACLE10G安装以后有一个默认的后台JOB,这个JOB会定期收集数据库的统计信息。在生产数据库上,这个是比较合适的。但是在开发数据库在红,在开发人员调试优化SQL语句的时候,有可能还需要随时随地手工运行统计信息的存储过程,以保证当前优化器得到的是正确而且最新的统计信息,这样才不会走错误的执行路径。假设用户是在scott这个schema下面工作,那么可以运行下面的语句
Sql>exec dbms_stats.gather_schema_stats(‘SCOTT’,10,TRUE);
这样就会把scott这个schema下最新的统计信息全部收集好。
上面的存储过程第一个参数是schema名,第2个参数是表明百分之多少的表记录被修改以后需要重新统计信息,打个比方:如果原来有1000条记录,如果增加或者删除100条(也就是10%)记录以后用户觉得就应该重新统计,那么就如上面的填写10。最后一个是表示:是否统计除表数据以外的其他数据,比如:索引的情况。如果是TRUE,那么,所有相关的信息都被统计,如果是FALSE,那么只统计表信息,其他的不统计。显然,第3个参数一般应该设置成TRUE。