2014-10-20 5 views
0

В последнем отчете об ошибке говорится, что вызываемый метод вызывает сбой службы, заставляющей ее перезапускаться. После устранения неполадок было обнаружено, что причиной является неприятный вызов Oracle SQL с переданными тысячами строк. Существует набор строк, передаваемых методу из внешней службы, которая часто составляет более 10 000 записей. Исходный код использовал предложение where в переданной коллекции, используя ключевое слово LIKE, которое, на мой взгляд, действительно очень плохое.Обработка больших объемов данных для включения в операторе выбора оракула

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 и передаваемые параметры и возвращает IEnumerable объектов типа, перенастроенных функцией Mapping.

SliceLeft - это метод расширения из другой внутренней вспомогательной библиотеки, которая возвращает только первую часть строки до количества символов, заданных параметром.


Причина, по которой LIKE заявление, видимо, используется, в том, что строки передается и строки в базе данных только гарантированно соответствовать первые 10 символов. Пример («XXXX000000-1» в передаваемых строках должен соответствовать записи базы данных, такой как «XXXX000000-8»).

Я считал, что положение в использовании SUBSTR будет более ЭФФЕКТИВНАЯ, чем использование нескольких положений 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 длиннее, чем сервер может анализировать в течение этих времен. Администратор баз данных предлагает сохранить все строки, переданные во временную таблицу, а затем присоединиться к этой временной таблице.

Учитывая, что совет, я изменил функцию:

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(); 
} 

Сейчас я получаю "ORA-00900: недопустимое заявление SQL". Это действительно расстраивает, как я могу правильно написать заявление SQL, которое поместит этот список строк во временную таблицу и затем будет использовать его в инструкции SELECT, чтобы вернуть список, который мне нужен?

+0

несколько быстрых заметок: если вы хотите использовать глобальную временную таблицу, вы создаете его один раз (за пределами какой-либо процедуры или метода, который использует его), а затем использовать он (вставить/выбрать/удалить) внутри процедуры. Кроме того, его сеанс специфичен, что может повлиять на его использование в зависимости от вашей настройки. Например, см. [Здесь] (http://stackoverflow.com/questions/9310860/how-to-create-and-use-temporary-table-in-oracle-stored-procedure/9310979#9310979) – tbone

ответ

1

Есть несколько возможных мест, которые могут вызвать эту ошибку, это означает, что «DECLARE GLOBAL TEMPORARY» является JAVA API, я не думаю, что .net имеет эту функцию. Вместо этого попробуйте «Создать глобальную временную таблицу». И я не знаю, мог ли ваш внутренний API обрабатывать несколько SQL-запросов в одном select sql. Насколько мне известно, класс ODP.net Command может выполнять только один sql за вызов. Более того, «create table» - это 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(); 
      } 
     } 
+0

Я домой, я попробую, когда вернусь в офис. – stephenbayer

+0

Я работаю с тем же sql в ответе с жестким кодированным значением, но не добавляю элементы из списка . В первый раз, когда я его запустил, все было хорошо, в следующий раз, когда я получаю конфликт с существующим объектом в оракуле, таблица temp не исчезла после завершения метода. Могу ли я добавить таблицу в конце? или запустить его в транзакции и откат после получения набора результатов. Как это обычно обрабатывается? – stephenbayer

+1

Вы получите сообщение об ошибке при запуске кода во второй раз, так как таблица не будет автоматически отбрасываться. В оракуле содержимое временной таблицы будет стерто как конец сеанса или конец транзакции (на основе выбранного вами варианта). Вы можете оставить стол после его использования. Но это не рекомендуется. Это вызовет некоторые проблемы, такие как одновременная проблема. Например, когда ваш первый поток создал таблицу и выполнил запрос, ваш второй поток хочет запустить тот же код и попытаться снова создать таблицу, это не удастся. Сохранять временную таблицу как обычную таблицу намного проще. – ivenxu

Смежные вопросы