Вот здесь можно узнать больше об базе данных cloudscape. Часть 3

Добавлено : 24 Oct 2008, 17:59

Продолжение. Вторая часть здесь.

Наиболее эффективным средством обеспечения целостности хранимых данных является использование триггеров, представляющих собой хранимые процедуры связанные с выполнением определенных действий над конкретными таблицами. Триггер автоматически активизируется при выполнении операций удаления, изменения, добавления данных. Возможности триггеров многократно превосходят ограничения и даже CHECK-выражения. Хотя опять-таки из-за тесной интеграции java и cloudscape существует замечательная возможность в качестве условия для CHECK использовать не только простейшее выражение, вроде «КАКОЕ-ТО ПОЛЕ >| < | = | <> КАКОМУ-ТО ЗНАЧЕНИЮ», но и более сложные выражения в которых могут участвовать даже вызовы метода java классов. Но к сожалению в которых запрещено использовать динамические параметры, т.е. если вы выполняете следующую операцию

PreparedStatement pstmt = conToDB.prepareStatement ("create table foo (fid int check (fid <> ? ))");
pstmt.setInt
(1 , 12345);
pstmt.executeUpdate
();

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

'?' may not appear in an ADD TABLE clause or a CHECK CONSTRAINT definition because 
it may return non-deterministic results.
conToDB.executeUpdate ("create table foo (fid int check fid in (select boo from bar))");

также запрещено использование функций получения текущей даты или времени (CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP). Запрещено использование подзапросов, хотя иногда так хочется написать следующую инструкцию

"create table foo (fid int check (fid in (select fid from foo)))"

А также запрещены определяемые пользователем агрегаты, кстати очень полезная вещь, о которой также поговорим но позже.

Возвращаясь к триггерам можно сказать, что там таких жестких ограничений нет и есть возможность использовать вложенные запросы, но нельзя динамические параметры, по той же причине что и ранее. Формат синтаксиса определения триггера очень простой

CREATE TRIGGER  ИМЯ_ТРИГГЕРА
{ BEFORE | AFTER } ; тут необходимо указать до или после внесения 
изменений будет вызываться данный триггер
{ INSERT | DELETE | UPDATE  ; вид операции при совершении которой 
триггер активизируется, тут же можно указать, что мы заинтересованы в 
активации триггера при 
выполнении изменений не для любых полей, а только для избранных с помощью необязательной части 
выражения OF ИМЕНА_КОЛОНОК разделенные запятой, если их более чем одна
ON ИМЯ_ТАБЛИЦЫ
; здесь может идти Referencing Clause
; триггер можно настроить так чтобы он вызывался каждый раз при изменении 
отдельной записи таблицы или только один раз при выполнении всего запроса 
указывая следующий параметр 
 “FOR EACH  ROW или  STATEMENT” 
ДЕЙСТВИЯ; тут вы сами знаете что писать
В качестве примера рассмотрим следующие две таблицы:Students” и “Groups. 
Структура данных таблиц была приведена ранее. Самый простой пример с 
использованием триггеров будет создание каскадного удаления информации о 
 
группах студентов, когда из этой группы были удалены все студенты 
CREATE TRIGGER delGroups
BEFORE DELETE
ON Students
REFERENCING OLD ROW AS DeletedStuds
FOR EACH ROW
DELETE FROM Groups  WHERE groupid = DeletedStuds.groupid

Предположим, что при удалении записи о студенте, необходимо проверять условие, что количество студентов в группе к которой принадлежал студент не должно быть менее 10. А при добавлении записей учитывать, что максимальное количество студентов не превосходит 40. Создать такое условие с использованием конструкции CHECK не возможно, а с помощью триггеров можно, но тоже придется похитрить. При запуске триггера не ожидается, что он возвращает некоторое логическое условие разрешающее или запрещающее выполнение изменения данных. Управление транзакциями с помощью конструкции ROLLBACK или COMMIT запрещено. Но если выбросить исключение во время выполнения триггера, то транзакция в контексте которой он выполнялся, будет прервана. Выбросить исключение из SQL несколько проблематично, но благодаря дружбе java и cloudscape можно будет разработать метод получающий в качестве параметра ссылку на текущий контекст соединения, потом проверяющий сколь угодно сложное условие и в случае необходимости выбрасывающее исключение. Предположим, что у нас есть следующий класс java.

package mp;
import sqlj.runtime.*;
import sqlj.runtime.ref.*;
import java.sql.*;
import oracle.sqlj.runtime.*;
import java.io.*;
 
public class TesterCo
{
  
public static void doTest (Connection co , int grId) throws SQLException
  
{
     
if (co == null)
        
throw new SQLException ("Sorry, but connection cannot be null");
      Statement stmt = co.createStatement
();
      ResultSet rs =  stmt.executeQuery
("select count (studentid) as costingr from" +       
           
"students where groupid =  "+ grId);
     
if (! rs.next()) throw new SQLException ("Sorry, but unknown error was while" +
           
"process you request");
     
int cnt = rs.getInt(1);
      rs.close
();
       
      rs =  stmt.executeQuery
("select * from students where groupid =  "+ grId);
      printToFileRs
(rs);
     
if ( cnt < 10 || cnt > 40) throw new SQLException ("Sorry, but conditions for" +
        
"table <> and <> are not valid; debug info\n"+
        
" Group = " + grId + ";  count students = " + cnt );
  
}
  
 
  
public  static void printToFileRs (ResultSet rs) throws SQLException
  
{
     
try{
        
java.io.PrintStream ps = new java.io.PrintStream (new
              
java.io.FileOutputStream("e:\\КУРСЫ\\tmp\\transi_"+ new java.util.Date ().getTime()+
              
".log" ));
         ResultSetMetaData mdrs = rs.getMetaData
();
         ps.println
(" --- Meta ---- ");
        
int cntf = mdrs.getColumnCount();
         ps.println
("count fields " + cntf);
        
for (int i=0; i < cntf; i++)
           
ps.println("Field # " + (i+1) + "; name " + mdrs.getColumnName(i+1) +
                 
"; sqltype " +
                  mdrs.getColumnTypeName
(i+1)+
                 
"; javatype " + mdrs.getColumnClassName(i+1));
         ps.println
("---- Data ---- ");
        
int rn = 0;
        
while (rs.next())
         {
           
ps.println("Record # " + rn++);
           
for (int i=0; i < cntf; i++)
              
ps.println( mdrs.getColumnName(i+1) + " = " + rs.getObject(i+1));
        
}
        
rs.close();
         ps.flush
();
         ps.close
();
     
}
     
catch (IOException ex)
      {
        
throw new SQLException (ex.getMessage());
     
}
   }
  
 
}

В таком случае мы можем создать триггер вызываемый при попытке удаления записи в таблице студентов и проверяющий условие допустимости операции.

С целью дополнительного контроля над набором данных во время выполнения транзакции я создал дополнительную функцию выполняющую журналирование действий над БД в файл. В данный файл сохраняется метаинформация о наборе данных и содержимое полей БД.

Следует обратить внимание на следующие моменты: прежде всего необходимо установить тип триггера реагирующий на изменение каждой строки, иначе не существовало бы удобного способа передать в функцию проверки номера группы к которой принадлежит студент, которого удаляют из БД. Разумеется запуск триггера для каждой строки несколько нерационален с точки зрения ресурсов машины, а особенное если это триггер в котором проводятся длительные и ресурсоемкие операции над таблицами. В большинстве случаев возможно и будет более правильно выполнить группировку проверки условий на допустимость выполнения операции сразу для всего набора данных. Но в этом случае способ добраться до значений отдельных полей удаляемых записей будет сложнее, поэтому об этом позднее. В крайнем случае, если существует необходимость выполнения очень длительных операций, то для выполнения их можно создать отдельный программный поток.

Важно что при выполнении журналирования набора данных из таблицы “Students” в том случае когда триггер имеет время срабатывания после изменения набора данных “AFTER” в таблице “Students” отсутствуют записи которые удалялись в целевой таблице. Если поставить тип триггера “BEFORE” то в выборке всех записей из таблицы “Students” будут присутствовать и те которые будут удаляться. На одну таблицу на одну операцию можно установить несколько триггеров, которые будут выполняться последовательно.

В приведенном примере в функцию doTest передавалось соединение с БД в контексте которой выполняестся операция изменения данных в таблице и триггер. Так как существуют жесткие ограничения на действия которые могут выполнять триггеры именно в текущей транзакции, например, запрещено выполнять инструкции DDL, управлять транзакциями. Если же такая необходимость действительно существует, то возможно установить новое соединение с БД в триггере. Теоретически такое соединение будет независимо от того в контексте которого выполняется транзакция, однако необходимо помнить что возможно возникновение deadlock из-за блокировки таблиц над которыми выполняется транзакция. Существует возможность при создании таблицы указать уровень блокировки или всей таблицы или только тех записей над которыми выполняется блокировка.

CREATE TABLE USERS_T(
   userid INT CONSTRAINT upk PRIMARY KEY, 
   fio char (50)
)
SET LOCKING = TABLE ; блокировка на уровне таблицы
CREATE TABLE USERS_R(
   userid INT CONSTRAINT upk2 PRIMARY KEY, 
   fio char (50)
)
SET LOCKING = ROW ; блокировка на уровне строк

При генерации исключений следует быть осторожными т.к. целостность транзакции обеспечивается на уровне всего запроса на изменение данных. Следовательно если у вас выполняется инструкция delete from “students where fio.trim().length () < 12” которая удаляет 13 записей. Пусть на операцию удаления установлен триггер вызывающий метода java, который проверяет условие допустимости удаления записи и если операция не допустима то выбрасывается исключение. Триггер работает для каждой строки. Если первые 12 записей прошли и исключение не было выброшено, а для последней 13-ой записи было выброшено исключение, то откатываются в исходное состояние также и первые 12 операций.

Далее для иллюстрации приводится пример кода вызывающего ошибку целостности транзакции. Создается триггер который обрабатывает изменения в таблице “Clarks”, внутри данного триггера вызывается метода java в котором создается другое соединение к БД и идет попытка изменить записи в таблице “Clarks”, когда генерируется исключение то он отлавливается и выводиться на экран.

public static void ErrorTransi (Connection co)
{
  
/*
    * Мы не используем существующее соединение, а создаем новое к таблице
    * Clarks, вся хитрость в том что мы пытаемся изменить ту таблицу, триггером
    * которой мы являемся, ожидается генерация исключения, нарушения блокировки
    * БД
    */
  
try{     
     
Connection con = DriverManager.getConnection
     
("jdbc:db2j:E:\\КУРСЫ\\kolya\\jbuilder_proj\\jdeveloper_oracle\\myBase;
            create=true"
,
           
"user" , "pass" );
      System.out.println
("OK get connection");
      Statement stmt = con.createStatement
();
      System.out.println
("OK create statement");      
      stmt.executeUpdate
("update clarks set address->zip= 'aaaa-zzzzz'");
      System.out.println
("OK update tab");            
  
}
  
catch (SQLException ex)
   {
     
while (ex != null){
        
if (ex.getErrorCode() == ExceptionSeverity.TRANSACTION_SEVERITY)
           
System.out.println("Severity: " + ex);
         ex = ex.getNextException
();
     
}
     
ex.printStackTrace();
  
}
}
 
public void initError () throws Exception
{
  
#sql [co] {delete from clarks where idClark = 5 };
}

при работе с триггерами наиболее важно добраться до множества строк которые удаляются или добавляются. Для этого можно воспользоваться предопределенными именами “old” и “new”, при использовании которых следует обязательно учитывать тип триггера. То есть триггер активизируется для каждой строки или для всего запроса. В первом случае можно ссылаться на отдельные поля записи (именной той для которой был вызван триггер), а во втором случае приходится работать с переменными “old” и “new” именно как с набором записей.

В качестве примера ниже триггер удаляющий из таблицы студентов всех студентов принадлежащих группе которую пользователь удаляет из таблицы групп (т.е. по сути каскадное удаление связанных записей). В случае когда триггер работает для каждой строки получатся следующий код:

CREATE TRIGGER TEST1
AFTER DELETE
ON GROUPS
FOR EACH ROW
DELETE FROM students WHERE GROUPID = old.groupid

Во втором варианте для ссылки “old” и “new” следует рассматривать именно как наборы записей.

CREATE TRIGGER TEST2
   AFTER DELETE
   ON GROUPS
   FOR EACH STATEMENT
   DELETE FROM students 
WHERE groupid IN (SELECT groupid FROM old)

Существуют и альтернативные способы добраться до набора записей добавляемых или удаляемых в текущей транзакции. Эти способы в основном имеет смысл использовать при вызове из триггера методов java. Первый подобный способ основан на использовании метода “getTriggerExecutionContext”. Далее приводится пример создания триггера в котором предполагается существование таблицы “crazyMath” в котором в поле “Prime” могут храниться только простые числа. Создается триггер вызывающийся при обновлении таблицы или внесении новой записи, в данном триггере вызывается метода “testPrime”, который проверяет новое значение поля на предмет его “простоты” (надеюсь, помним, какое число является простым). Если новое число не является простым, то будет сгенерировано исключение с целью прервать транзакцию и отменить изменение данных.

public static void isPrime(int prime) throws SQLException{
  
int n =  (int)(Math.sqrt(prime) + 1);
  
for (int i=2; i <= n ;i++)
     
if ( prime % i == 0)
        
throw new SQLException ("Sorry, but number <<"+ prime+ ">>" +
        
"isnt prime and cannot be stored in table crazymath");
}

Данный статический метод находится в классе mp2.Crazy и для привязки триггера к добавлению новых записей в таблице “Crazy” используется следующий код.

CREATE TRIGGER MTC
AFTER INSERT
ON CRAZYMATH
FOR EACH STATEMENT
CALL mp2.Crazy::isPrime (Factory::getTriggerExecutionContext().getNewRow().getInt('prime'))

В данном примере мы обращались к новой записи используя метод “getNewRow”, возможно разумеется добраться и до старого значения поля если, конечно, оно существует, с помощью метода “getOldRow”. Следующим шагом будет создание триггера обрабатывающего целый набор записей. Вернемся к нашему старому примеру с таблицами “student” и “groups” при удалении записи о группе из таблицы “groups” следует автоматически удалять все записи о студентах обучающихся в данной группе. Технически это будет сделано с помощью “VTI” курсора который можно получить с помощью создания объекта типа “TriggerOldTransitionRows”.

public static void delStuds(Connection con) throws SQLException{
  
PreparedStatement pstmt = con.prepareStatement(
        
"DELETE FROM students WHERE GROUPID IN "+
  
"(SELECT GROUPID FROM NEW TriggerOldTransitionRows() AS OLDGROUPS)");
    
   pstmt.executeUpdate
();
   pstmt.close
();
}

Выше приводится полнофункциональный пример кода триггера удаляющего студентов группы. Особо следует обратить внимание на то что в инструкции sql присутствует фрагмент “AS OLDGROUPS” . В общем случае когда вы хотите выполнить следующую инструкцию “select * from students where groupid in (select groupid from groups where studsAvgBall >= 4)” использовать инструкцию “as имя_набора_данных” не обязательно. Выборка будет отлично работать, даже если вы и не укажите данный параметр, но в случае создания списка строк на базе VTI обязательно следует указать имя курсора. Аналогично, можно обращаться к набору новых записей используя vti-таблицу “TriggerOldTransitionRows ”.

Признаком хорошей СУБД является поддержка транзакций, а раз cloudscape – хорошая СУБД, то и транзакции реализованы тут на очень высоком уровне. Транзакция, или как еще ее называют LogicalUnitOfWork (LUW) это группа операций которые либо все выполняются либо не выполняется никакая операция из этой группы, обоснование и объяснение за чем это нужно пропустим, все таки предполагается наличие у читателя общих сведений об sql и базах данных вообще. Существуют различные уровни разделения транзакции каждый из которых позволяет избежать ряда аномалий. Существуют следующие виды аномалий:

1. “Грязное чтение” - два потока одновременно начинают транзакции. 
Первый поток делает запрос на обновление “update students set stip = stip*1.5. 
Второй же поток во время выполнения первого видит измененные данные 
которые еще не были подтверждены первым потоком.
2. «Неповторяющееся чтение». Суть в том, что поток делает две абсолютно 
одинаковые инструкции “select * from students where avgBall < 4” и получает 
различные наборы данных. Данная аномалия является производной из первой аномалии, 
и обуславливается наличием второго потока выполняющего одновременно с первым 
изменение набора данных.
3. «Фантомы». В течении одной транзакции то появляются, то исчезают наборы записей.
Избежать аномалий можно с помощью механизма изоляции транзакций. 
Существуют следующие уровни изоляции, которые в совокупности с используемым для 
обрабатываемой таблицей уровнем блокировки (на уровне всей таблицы или 
на уровне обрабатываемой записи) позволяют избежать определенного вида аномалий.

В JDBC определены следующие уровни изоляции транзакций:

- TRANSACTION_READ_UNCOMMITTED (ANSI level 0) 
- TRANSACTION_READ_COMMITTED (ANSI level 1) 
- TRANSACTION_REPEATABLE_READ (ANSI level 2) 
- TRANSACTION_SERIALIZABLE (ANSI level 3) 


Следует только помнить, что самый нижний уровень (0) в cloudscape не поддерживается. Устанавливая определенный уровень изоляции транзакции мы можем указать насколько сильно действия которые выполняются в рамках различных запросов пользователя (или нескольких пользователей) будут изолированы друг от друга. Изменение уровня изоляции сложное решение на одной чаше которого будет находиться производительность БД, обслуживающей одновременно многие сотни подключений, а на второй находится целостность данных и отсутствие тех странных аномалий и плавающих ошибок (которые то есть, а то их и нет).


В данной таблице которая была взята из официальной документации шедшей вместе с cloudscape идет описание соотношения уровней изоляции, уровней блокировки и аномалий операций над таблицами. Аномалии соответствующие определенному уровню блокировки таблицы и уровня изоляции транзакции

Isolation Level Table-Level Locking Row-Level Locking
TRANSACTION_READ_UNCOMMITTED “Грязное чтение”, “Неповторяющееся чтение”, “Фантомное чтение” “Грязное чтение”, “Неповторяющееся чтение”, “Фантомное чтение”
TRANSACTION_READ_COMMITTED “Неповторяющееся чтение”, “Фантомное чтение” “Неповторяющееся чтение”, “Фантомное чтение”
TRANSACTION_REPEATABLE_READ “Фантомное чтение” не допустимо из-за полной блокировки таблицы. “Фантомное чтение”
TRANSACTION_SERIALIZABLE Нет аномалий Нет аномалий


Для того чтобы проверить какие уровни изоляции транзакций поддерживает ваша СУБД следует либо читать документацию либо воспользоваться приведенным ниже фрагментом кода в котором с помощью объекта метаинформации можно получить полный список возможностей СУБД.

public void testTransLevels(String driver, String url ) throws SQLException, ClassNotFoundException
{
  
Class.forName(driver);
   Connection con = DriverManager.getConnection
(url);
   DatabaseMetaData dbm = con.getMetaData
();
   System.out.println
("TRANSACTION_NONE " +
         dbm.supportsTransactionIsolationLevel
(Connection.TRANSACTION_NONE));
   System.out.println
("TRANSACTION_READ_COMMITTED " +
         dbm.supportsTransactionIsolationLevel
(Connection.TRANSACTION_READ_COMMITTED));  
   System.out.println
("TRANSACTION_READ_UNCOMMITTED " +
         dbm.supportsTransactionIsolationLevel
(Connection.TRANSACTION_READ_UNCOMMITTED));
   System.out.println
("TRANSACTION_REPEATABLE_READ " +
         dbm.supportsTransactionIsolationLevel
(Connection.TRANSACTION_REPEATABLE_READ));
   System.out.println
("TRANSACTION_SERIALIZABLE " +
         dbm.supportsTransactionIsolationLevel
(Connection.TRANSACTION_SERIALIZABLE));
   con.close
();  
}

Каким образом можно влиять на уровень транзакции. Прежде всего необходимо помнить, что по умолчанию используется уровень изоляции “TRANSACTION_READ_COMMITTED ”. Однако есть возможность переопределить принятое по умолчанию значение используя системное свойство “db2j.language.defaultIsolationLevel”, присваивая ему ряд значений “READ_COMMITTED, SERIALIZABLE, REPEATABLE_READ”. Возможно изменение значения данного свойства на этапе выполнения приложения используя инструкцию sql “set transaction isolation level”. Например “SET TRANSACTION ISOLATION LEVEL SERIALIZABLE“.

И даже более того существует деликатная возможность изменять уровень изоляции транзакции на этапе выполнения конкретного запроса sql используя необязательный параметр инструкции “select спецификация_выбора_данных at isolation значение_уровня_изоляции”, например, “ select * from students where avgBall < 4 at isolation read commited”.

При выборе уровня блокировки на уровне таблицы следует помнить, что позволяет избежать большего числа аномалий, но в многопользовательских системах неизбежно приводит к снижению производительности и росту числа запросов sql ожидающих в очереди. Очевидно, что возможности мультипроцессорной обработки не используются.

Более детальное рассмотрение транзакций требует общего представления о задачах синхронизации и способах их решения. Существуют следующие классические задачи синхронизации.

1. Задача взаимного исключения, в данном случае существует некоторый 
неразделяемый ресурс и некоторое количество потребителей заинтересованных в нем. 
Как только один процесс захватил ресурс остальные должны ждать пока первый не освободит его.
2. Задача “Производители-потребители”. В наиболее простой формулировке существует 
только один потребитель и только один производитель некоторого ресурса, 
которые выполняются одновременно. Взаимодействие же между ними идет через 
специальный буфер, вспомогательную переменную емкостью одна единица продукции. 
В общем случае следует согласовать скорость работы обоих участников. 
В ситуации когда производитель работает быстрее потребителя возможно ситуация 
что за время в течении которого потребитель потребил одну единицу продукции 
производитель успел произвести несколько единиц, которые помещал в буфер емкостью 
одна единица, а следовательно затирал старые значения, которые никогда не 
были получены и обработаны потребителем, (представьте себе банковскую систему 
в которой были потеряны переводы денег из-за такой разницы в скорости работы). 
Проблемы возникающие в случае если потребитель работает быстрее производителя, 
также неприятны. Будут обработаны несколько раз одни и те же данные. 
В более сложной формулировке данная задача предполагает наличия нескольких 
производителей и потребителей и возможность варьировать размер буфера обмена.
3. Задача “Читатели-писатели”. В данной задаче предполагается наличие 
нескольких потоков, которые можно условно разделить на читающие информацию из 
некоторой общей переменной и записывающей туда информацию. Очевидно что данная 
задача требует взаимоисключения потоков, но более деликатную чем в задаче первой. 
Ведь если несколько читателей пытаются одновременно взять значение из промежуточной 
переменной то несогласованности не возникает. Но как только к буферу обращается 
писатель, то он должен исключать не только читателей, но и писателей.
4. Задача “Обедающие философы”. Вкратце ее суть можно представить так: 
есть 3 мудрых китайских философа, которые собрались перекусить. 
Перед ними находится общее блюдо с лапшой и три палочки. Для того чтобы начать 
есть философ должен взять в руки две палочки. Если ему удалось ухватить только 
одну палочку, то он будет ожидать вторую которую должен будет освободить другой философ, 
как только закончит прием пищи. Однако представим себе ситуацию, когда все три философа 
одновременно взяли в руки по одной палочке и теперь терпеливо ждут, когда же освободится 
следующая палочка. А раз сосед никогда не отдаст палочку раз он сам ждет когда же ему 
отдадут вторую необходимую для еды палочку, следовательно первому, второму, и разумеется 
третьему философу  придется ждать вечно и умереть от голода. Разумеется, что это одни 
из немногих, но достаточно интересные задачи, которые приходится постоянно решать 
разработчикам операционных систем, СУБД, и других приложений работающих 
в многопользовательском режиме.

Возвращаясь к cloudscape можно сказать, что существует три вида блокировки ресурсов. Первый способ это монопольное использование. Суть очень проста – один и только один клиент может работать с БД. Разумеется, что несмотря на свою простоту и легкость в реализации, данный подход не имеет практического применения при построении распределенных и многопользовательских систем. Второй вид блокировки – так называемая общая блокировка, она похожа на действия в случае второй классической задачи синхронизации “читатели-писатели”, когда читатели могли одновременно обращаться к набору данных. Следует понимать, что даже когда вы делаете обычный запрос на выборку данных, то неявно уже начинается новая транзакция, завершаемая как только вы доходите до последней записи. Существует также и третий вид блокировки – это “update lock”. Данная блокировка возникает в случае когда создается курсор для обновления. Эта блокировка может перейти в эксклюзивную блокировку когда выполняется инструкция изменения данных dml.

При работе с транзакциями и синхронизацией обязательно следует помнить о deadlock. Пример возможной ситуации deadlock был приведен в примере с обедающими философами когда захват ресурса и нежелание поделиться с другими приводило к гибели всех философов. Как избежать подобных ситуаций? Часть проблем может отсечь сам разработчик за счет логики работы программы, когда приложению необходимы одновременно две или более таблицы то можно сделать попытку их захвата, а если не удалось захватить какую-то из них, то обязательно следует освободить те которые удалось захватить и не жадничать. Разумеется данный подход не рационален за счет напрасной траты ресурсов времени. В общем случае возможны два подхода как избежать deadlock-ов либо мы не допускаем их, либо своевременно обнаруживаем и принимаем меры по исправлению.

К первому способу можно отнести использование конструкции “lock table”. Во втором же способе достаточно эффективный контроль ведет сама cloudscape, когда устанавливает timeout времени выполнения захвата ресурса. На время ожидания можно влиять изменяя системное свойство “db2j.locks.waitTimeout”, значение данного свойства по умолчанию 180 секунд. Если же установить значение “–1”, то ожидание будет вечным, лимит времени будет снят. В качестве своеобразной иллюстрации механизма блокировки можно будет разработать приложение, которое создает n-ое количество клиентов обращающихся к таблицам БД, причем сам характер их обращения предполагает наличие блокировок. И отображающий кривую зависимости количества конфликтов от количества одновременных запросов. Для проверки того что запрошенная операция была отменена вследствие возникшего deadlock можно воспользоваться следующим фрагментом кода.

try{
//   Некоторые действия
}
catch (SQLExcetion e){
  
if (e.getSQLState.equals(“40001”)) значит возникла блокировка
}