Детектив про членистоногих

Submitted by igorla on Wed, 07/23/2008 - 06:22

Нередко на работе я чувствую себя детективом. Сначала надо собрать улики, а потом из этого набора установить "убийцу".
Вот так и было последние 5 дней.

Есть вот такой запрос (в упрощенном виде, конечно):
select name, sum(cost) total_cost from fact_table
group by name
order by total_cost

Работает? Конечно работает, и приносит результаты:
IBM      50.0
MS        60.0
HP         70.0
...

Т.к. строчек может быть много, то добавляется paging (база данных - Oracle)
select *   from (select row_.*, rownum rownum_  from (
select name, sum(cost) total_cost from fact_table
group by name
order by total_cost
) row_ where rownum < 10 )  where rownum_ >=0

Тоже работает. Но не везде. В Тоаде работает, через JDBC работает, со Spring-oм работает. А когда бежит из EAR-a в JBoss, то приносит вот такие загадочные результаты:
IBM      50.0
IBM      60.0
IBM      70.0

Кто виноват и что делать?
Ответ на первый вопрос в 90% случаях превращает второй в риторический.

Начинаем собирать улики.
0. Поиск в инете - ничего.
1. Если сортировать по обычному полю (по name, например), то все в порядке.
2. Повторяющиеся данные выводятся только в колонках типа VARCHAR. Ага, уже что-то. Делаем п.0 - ничего.
3. Начинаем упрощать запрос. Выясняется, что если откинуть обертку paging с rownum, то снова все в порядке. Делаем п.0 - нахожу несколько похожих случаев, некоторые даже с решениями. Пробуем, проверяем- dead end.
4. Ухожу в глубокий debug. Т.к. почти везде open-source, то спускаюсь довольно глубоко, пока не упираюсь в закрытый код оракловского драйвера. Декомпилирую классы, не сильно помогает, но одно ясно - где-то у них слетает указатель во внутреннем массиве. Попытки менять драйвера  и spring различных версий, просмотр лога драйвера - nothing. К тому же этот же драйвер превосходно работает в standalone application. И никакой interceptor не будет менять внутренние массивы драйвера. Похоже, налицо преступный сговор Oracle и JBoss; снова п.0 - ничего.

 --  Мозговой штурм с моим цеветом и VP RnD - куча идей, все проверяются, ничего нового.

5. Замена thin driver na oci в datasource.xml. Не помогает. Вот тут-то внимательней взглянуть бы на каждую строчку в определинии datasource...
6. Подсоединение с другим юзером, к другим базам - те же результаты.
7. Запуск application server на Линуксе (ну что можно ждать хорошего от винды ;) - все то же, все те же.
8. Релевантный код максимально упрощается, VP RnD запускает его в WARe на своем сервере - проблема исчезает - УРА! (а JBoss, таки нашел себе алиби..)
9. Тот же самый WAR на моем сервере - не работает. Двойное УРА!!
А. Сличаются библиотеки, конф-файлы - одинаковые. Хотя нет, datasource.xml разный! 

Находится подозреваемый и превращается в обвиняемого тут же. Вот такая строчка, выполняемая при каждом создании connection:
   alter session set NLS_SORT=BINARY_CI  NLS_COMP=LINGUISTIC
Была добавлена где-то год назад и обеспечивала корректную работу в базе в case-insensitive режиме.

Вот такой интересный баг в Oracle был найден...
"Обезврежен он, и даже он пострижен и посажен.." А жаль, у нас было еще много интересных идей: пересадить аппликацию на Websphere, поменять базу на DB2, выкинуть сервер из окна..


Что делать?
Не так все просто. Замена LINGUISTIC на ANSI решает ЭТУ проблему. Но возвращает старую - поиск работает только как case-sensitive :(
Есть несколько вариантов - использование UPPER(), дублирование имен в добавочную колонку в upper-case, покупка всего Oracle для установления urgent priority для этого бага..

Upd. Написал в Oracle, через день получил от них письмо, что они чинят этот баг..





Comments

victoria_lag

Wed, 07/23/2008 - 04:00

да, круто. А вы не используете какой-нибудь Hibernate со Спрингом?

Да, юзаем. В этом конкретном модуле Spring. В других и Hibernate тоже используем. Но это ведь всего лишь оболочка для драйвера..

ext_112781

Sat, 07/26/2008 - 04:00

Не. крута адназначна!
Но только, кажется мне что задачу эту можно было решить не залазя в эти дебри. Вам нужен был pagination? И вы работаете с Hibernate?
Если оба раза "да", то чё б не использовать pagination самого Hibernate? А если "нет", то я шо-то упустил, звыняйте. На всякий случай, вот, честно передрал из Hibernate 3.2.2 reference:
"...
Pagination
If you need to specify bounds upon your result set (the maximum number of rows you want to retrieve and / or
the first row you want to retrieve) you should use methods of the Query interface:
Query q = sess.createQuery("from DomesticCat cat");
q.setFirstResult(20);
q.setMaxResults(10);
List cats = q.list();
Hibernate knows how to translate this limit query into the native SQL of your DBMS.
Scrollable iteration
If your JDBC driver supports scrollable ResultSets, the Query interface may be used to obtain a Scrolla-
bleResults object, which allows flexible navigation of the query results.
Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " +
"order by cat.name");
ScrollableResults cats = q.scroll();
if ( cats.first() ) {
// find the first name on each page of an alphabetical list of cats by name
firstNamesOfPages = new ArrayList();
do {
String name = cats.getString(0);
firstNamesOfPages.add(name);
}
while ( cats.scroll(PAGE_SIZE) );
// Now get the first page of cats
pageOfCats = new ArrayList();
cats.beforeFirst();
int i=0;
while( ( PAGE_SIZE > i++ ) && cats.next() ) pageOfCats.add( cats.get(1) );
..."

Именно в этом модуле hibernate мы не используем, а spring+jdbc.
Кроме того, обрати внимание: "Hibernate knows how to translate this limit query into the native SQL of your DBMS." - т.е. скорее всего, снова уперлись бы в ту же самую пронблему, т.к. баг в самом Оракле..