最近的错误报告指出正在调用的方法正在崩溃服务,导致它重新启动。经过故障排除后,发现原因是一个令人讨厌的Oracle SQL调用,传递了数千个字符串。从外部服务传递给方法的字符串集合通常超过10,000条记录。原始代码使用LIKE关键字对传递的集合使用了where子句,我认为这非常非常糟糕。
public IList<ContainerState> GetContainerStates(IList<string> containerNumbers)
{
string sql =
String.Format(@"Select CTNR_NO, CNTR_STATE FROM CONTAINERS WHERE CTRN_SEQ = 0 AND ({0})",
string.Join("OR", containerNumbers
.Select(item => string.Concat(" cntr_no LIKE '", item.SliceLeft(10), "%' ")))
);
return DataBase.SelectQuery(sql, MapRecordToContainerState, new { }).ToList();
}
澄清使用的内部方法可能令人困惑:
DataBase.SelectQuery是一个使用泛型的内部库方法,它传递sql字符串,一个将记录映射到.NET对象的函数,以及传递的参数,并返回由Mapping函数重新调整的IEnumerable类型的对象。
SliceLeft是来自另一个内部帮助程序库的扩展方法,它只返回字符串的第一部分,直到参数指定的字符数。
明显使用LIKE语句的原因是传递的字符串和数据库中的字符串仅保证匹配前10个字符。示例(&#34; XXXX000000-1&#34;在传递的字符串中应与数据库记录匹配,如&#34; XXXX000000-8&#34;)。
我相信使用SUBSTR的IN子句比使用多个LIKE子句更有效,并用以下代码替换:
public IList<ContainerRecord> GetContainerStates(IList<string> containerNumbers)
{
string sql =
String.Format(@"Select CTNR_NO, CNTR_STATE FROM CONTAINERS WHERE CTRN_SEQ = 0 AND ({0})",
string.Format("SUBSTR(CNTR_NO, 1, 10) IN ({0}) ",
string.Join(",", containerNumbers.Select(item => string.Format("\'{0}\'", item.SliceLeft(10) ) ) )
)
);
return DataBase.SelectQuery(sql, MapRecordToContainerState, new { }).ToList();
}
这有点帮助,我的测试中出现的问题较少,但是当传递了大量记录时,仍然会抛出异常并发生核心转储,因为SQL比服务器在这些时间内可以解析的时间长。 DBA建议保存传递给临时表的所有字符串,然后加入该临时表。
鉴于这个建议,我将功能改为:
public IList<ContainerRecord> GetContainerStates(IList<string> containerNumbers)
{
string sql =
@"
CREATE TABLE T1(cntr_num VARCHAR2(10));
DECLARE GLOBAL TEMPORARY TABLE SESSION.T1 NOT LOGGED;
INSERT INTO SESSION.T1 VALUES (:containerNumbers);
SELECT
DISTINCT cntr_no,
'_IT' cntr_state
FROM
tb_master
WHERE
cntr_seq = 0
AND cntr_state IN ({0})
AND adjustment <> :adjustment
AND SUBSTR(CTNR_NO, 1, 10) IN (SELECT CNTR_NUM FROM SESSION.T1);
";
var parameters = new
{
@containerNumbers = containerNumbers.Select( item => item.SliceLeft(10)).ToList()
};
return DataBase.SelectQuery(sql, MapRecordToContainerState, parameters).ToList();
}
现在我得到一个&#34; ORA-00900:无效的SQL语句&#34;。这真的令人沮丧,我怎样才能正确编写一个SQL语句,将这个字符串列表放入一个临时表中,然后在SELECT语句中使用它来返回我需要的列表?
答案 0 :(得分:1)
有几个可能的地方可能会导致这个错误,它会接触到&#34; DECLARE GLOBAL TEMPORARY&#34;是一个JAVA API,我不认为.net有这个功能。请尝试&#34;创建全局临时表&#34;代替。并且,我不知道您的内部API是否可以在一个select sql中处理多个SQL。据我所知,ODP.net Command类每次调用只能执行一个sql。而且,&#34;创建表&#34;它是一个DDL,因此它有自己的事务。我无法看到任何理由将它们放在同一个sql中执行。以下是ODP.net的示例代码,
using (OracleConnection conn = new OracleConnection(BD_CONN_STRING))
{
conn.Open();
using (OracleCommand cmd = new OracleCommand("create global temporary table t1(id number(9))", conn))
{
// actually this should execute once only
cmd.ExecuteNonQuery();
}
using (OracleCommand cmd = new OracleCommand("insert into t1 values (1)", conn)) {
cmd.ExecuteNonQuery();
}
// customer table is a permenant table
using (OracleCommand cmd = new OracleCommand("select c.id from customer c, t1 tmp1 where c.id=tmp1.id", conn)) {
cmd.ExecuteNonQuery();
}
}