(23) Working with Large Objects


  • Let you store text, images, music, and video in the database.
  • You can store character large objects in CLOB columns, and binary large objects inside the database as BLOB columns or outside the database as BFILE (binary file) columns.
  • BFILE columns: store a locator that points to the physical location of an external file.

  • LOBs can hold up to a maximum of 8 to 128 terabytes, depending on how you configure your database.
  • A call to the DBMS_LOB.GET_STORAGE_LIMIT function tells you your database maximum LOB size.

set serveroutput on
declare 
 var1 clob :=  'some string'; 
 var2 blob := hextoraw( '43'||'421'|| '52');
 result number;
begin
 result := dbms_lob.get_storage_limit(var1);
 dbms_output.put_line('CLOB maximum limit: '|| 
                        to_char(result/1024/1024/1024, '999,999') || ' Gigabytes');
 dbms_output.put_line('CLOB length: ' || dbms_lob.getlength(var1));
 
 result := dbms_lob.get_storage_limit(var2);
 dbms_output.put_line('BLOB maximum limit: '|| 
                       to_char(result/1024/1024/1024, '999,999') || ' Gigabytes');
 dbms_output.put_line('BLOB length: ' || dbms_lob.getlength(var2));
end;

anonymous block completed
CLOB maximum limit:   16,264 Gigabytes
CLOB length: 11
BLOB maximum limit:   32,528 Gigabytes
BLOB length: 4

what for?
  • simple structured data => relational tables
  • complex structured data => object-relational constructs (collections, references, UDTs)
  • semi-structured data => logical structure not typically interpreted by the database. LOBs can be used here.
  • Examples of semi-structured data include document files such as XML documents or word processor files.
  • unstructured data => photos, videos. LOBs can be used here.


On 11g R1: SecureFiles. LOBs reengineered
  • You can now define BLOB, CLOB, or NCLOB columns as SecureFiles when you create a table or alter it.
  • SecureFiles are declared with special storage parameters that let you encrypt, compress, and deduplicate them.
  • Oracle 11g lets you store SecureFiles in BLOB, CLOB, and NCLOB columns.
  • They work on a principle of Transparent Data Encryption (TDE) and use an Oracle Wallet as the encryption key.


  • BLOB, CLOB and NCLOB can be stored in the database (internal) or in external files.
    Internal LOBs can be either persistent or temporary.
    • Persistent LOB: Exists in a table row in the database.
    • Temporary LOB: Created when you instantiate a LOB only within the scope of your local application.
  • External LOBs are data objects stored in operating system files, outside the database tablespaces.
  • The database accesses external LOBs using the SQL data type BFILE, which is the only external LOB data type.
  • BFILEs are read-only data types.
  • External LOBs do not participate in transactions. Any support for integrity and durability must be provided by the underlying file system as governed by the operating system.

COPY vs. REFERENCE semantics
  • Internal LOBs use copy semantics.: both the LOB locator and LOB value are logically copied during insert, update, or assignment operations.
  • External LOBs use reference semantics.: only the LOB locator is copied during insert operations.

A LOB (BLOB, CLOB, NCLOB) column can be in one of three states
NULLThe table cell is created, but the cell holds no locator or value. (The column contains a NULL value)
EmptyThe column contains a LOB locator that has no value. Length of LOB is zero.
PopulatedThe column contains a LOB locator and a value. A call to dbms_lob.getlength here returns a postive value.

CLOB and NCLOB
The CLOB datatype is an object type, therefore it requires implicit or explicit construction of an object instance.

Declaring a CLOB:
var1 CLOB;                  -- Declare a null reference to a CLOB.
var1 CLOB := empty_clob();  -- Declare an empty CLOB.
var2 CLOB := 'some_string'; -- Declare a CLOB with a string literal.

Manipulating a CLOB column
create table item ( 
 item_id number, 
 item_title varchar2(20), 
 item_desc  clob,
 item_photo bfile);

desc item
Name       Null Type         
---------- ---- ------------ 
ITEM_ID         NUMBER       
ITEM_TITLE      VARCHAR2(20) 
ITEM_DESC       CLOB         
ITEM_PHOTO      BFILE()    


INSERT into item values
  (1, 'Ulysses', empty_clob(), null);
1 row inserted.

-- This approach limits the amount of data that can be 
-- inserted into a CLOB column to is limited to 4K (SQL) or 32K (PL/SQL)

UPDATE item
SET item_desc = 'The Lord of the Rings is a high fantasy epic written by '||  
'philologist and University of Oxford professor J. R. R. Tolkien. The story '||
'began as a sequel to Tolkien's earlier, less complex children's fantasy '||
'novel The Hobbit (1937), but eventually developed into a much larger work. '||
'It was written in stages between 1937 and 1949, much of it during the '||
'Second World War.[1] It is the second best-selling novel ever written, '||
'with over 150 million copies sold.'
WHERE item_id =1;

Alternatively, you can use the DBMS_LOB package

Using DBMS_LOB, you can:

(a) Insert a new row and initialize the CLOB column with a call to empty_clob()
(b) Read chunks from the file and insert into the CLOB Column, appending each new chunk until copying the entire file (up to 128Tb).
(c) To open and read the file at the OS level from within the PLSQL Package, you need to create a DB Directory object and grant permission for the user to read from that directory.
(d) You may use conditional compilation directives to insert debut information..

-- You need to connect as SYSTEM to perform the following two steps: 
-- (1) create a virtual directory. should be conn as system
create directory generic as '/tmp/clob-file'; 

-- grant read permissions on the directory to the user;
grant read on directory generic to dev2;

-- Create procedure to load the file into the CLOB column.
CREATE OR REPLACE PROCEDURE load_clob_from_file
 (src_file_name in varchar2,
  table_name in varchar2,
  column_name in varchar2,
  pk_name in varchar2,
  pk_value in varchar2) is
  
  -- define local vars for dbms_lob.loadclobfromfile proc
  des_clob clob;
  -- the BFILENAME function secures the directory path for 
  -- the GENERIC directory from the database dictionary and 
  -- returns the absolute filename
  src_clob bfile := bfilename('GENERIC', src_file_name);
  des_offset number:= 1;
  src_offset number := 1;
  ctx_lang number := dbms_lob.default_lang_ctx;
  warning number;
  -- declare var to hold the size of the file to be imported.
  src_clob_size number;
  stmt varchar2(2000);
BEGIN
  -- open source file and reads it into a BFILE datatype
  if dbms_lob.fileexists(src_clob) = 1 and 
     NOT dbms_lob.isopen(src_clob)= 1 THEN
     src_clob_size := dbms_lob.getlength(src_clob);
     $IF $$DEBUG $THEN
       dbms_output.put_line('Opening CLOB. Size: '|| src_clob_size);
     $END
    dbms_lob.open(src_clob, DBMS_LOB.LOB_READONLY);
  end if;
  -- assign a dynamic string to the stmt var
  -- Initializes the CLOB column to an empty_clob()
  -- and returns a reference to the column into an 
  -- output variable
  stmt :=  'UPDATE ' ||table_name||' '
       ||  'SET    '||column_name||' = empty_clob()'
       ||  'WHERE  '|| pk_name||' = '||''''||pk_value||''' '
       ||  'RETURNING '||column_name||' INTO :locator';
  -- execute dynamic stmt
  -- the bind variable :locator is assigned to the output var
  $IF $$DEBUG $THEN
    dbms_output.put_line('Preparing to initialize CLOB column');
  $END
  EXECUTE IMMEDIATE stmt USING OUT des_clob;
  $IF $$DEBUG $THEN
    dbms_output.put_line('Initialization successful..');
  $END
  -- read and write file to CLOB, close source file and commit;
  -- the call to DBMS_LOB.LOADCLOBFROMFILE. The procedure 
  -- copy the contents of the file into the table column.
  -- This R/W operation bypasses the 32Kb handling limitation of PL/SQL
  dbms_lob.loadclobfromfile( 
           dest_lob    => des_clob,
           src_bfile   => src_clob,
           amount      => dbms_lob.getlength(src_clob),
           dest_offset => des_offset,
           src_offset  => src_offset,
           bfile_csid  => dbms_lob.default_csid,
           lang_context => ctx_lang,
           warning     => warning);      
  -- close the OS file.   
  dbms_lob.close(src_clob);
  
  IF src_clob_size = dbms_lob.getlength(des_clob) THEN
     $IF $$DEBUG $THEN
       dbms_output.put_line('Success!');
     $END
     commit;
  ELSE
    $IF $$DEBUG $THEN
      dbms_output.put_line('Failure.');
    $END
    RAISE dbms_lob.operation_failed;
  END IF;

EXCEPTION
  WHEN OTHERS THEN
    dbms_output.put_line('Error stack: '|| dbms_utility.format_error_stack());
    dbms_output.put_line('Error Backtrace: '|| dbms_utility.format_error_backtrace());
END load_clob_from_file;
/

PROCEDURE load_clob_from_file compiled

-- Alter session to set conditional compilation directive 
-- and turn on debugging 
SQL> alter session set plsql_ccflags = 'DEBUG:TRUE';


SQL> set serveroutput on 
SQL> BEGIN
 load_clob_from_file (
   src_file_name    => 'ulysses.txt',
   table_name       => 'item',
   column_name      =>  'item_desc',
   pk_name          => 'item_id',
   pk_value         => to_char(1));
END;
/
anonymous block completed
Opening CLOB. Size: 11989153
Preparing to initialize CLOB column
Initialization successful..
Success!


















Q&As: Parallelism




(Q) What types of statements and tasks can be improved through parallel execution?

  • Queries requiring: large table scans, joins, or partitioned index scans
  • Creation of large indexes
  • Creation of large tables (and Materialized Views)
  • Bulk Inserts, Updates, Meges and Deletes
  • Scanning large objects (LOBs)


(Q) What are some of the characteristics of systems that can benefit from parallel execution?

  • Have SMPs, clusters, or MPPs
  • Sufficient IO bandwidth
  • Underutilized CPUs (<30%)
  • Enough memory free


(Q) When can OLTP systems mostly benefit from parallel execution?

  • OLTP systems may benefit during batch processing and schema maintenance operations.


(Q) What are three key parameters controlling automatic parallel execution?

  • PARALLEL_DEGREE_LIMIT
  • PARALLEL_DEGREE_POLICY = { MANUAL | LIMITED | AUTO } <== MANUAL => disabled, AUTO => Auto DOP on
  • PARALLEL_MIN_TIME_THRESHOLD = AUTO (default, about 10sec)


(Q) What is Intra-parallelism and Inter-parallelism?

  • Intra-operation parallelism: parallelism of an individal operation
  • Inter-operation parallelism: parallelism between operations in a data flow tree (in an execution plan)


(Q) What is the default degree of parallelism assumed by an Oracle Server?

  • By Default, PARALLEL_DEGREE_POLICY = Manual (NO PARALLELISM)
  • By default, the system only uses parallel execution when a parallel degree has been explicitly set on an object or if a parallel hint is specified in the SQL statement
  • If PARALLEL_DEGREE_POLICY = AUTO, then
    • Single Instance: DOP = PARALLEL_THREADS_PER_CPU X CPU_COUNT
    • RAC: DOP = PARALLEL_THREADS_PER_CPU X CPU_COUNT x INSTANCE_COUNT
    • important: in a multiuser environment, default parallelism is not recommended


(Q) What is the function of the PARALLEL_DEGREE_POLICY parameter? What vaules it takes?

  • PARALLEL_DEGREE_POLICY = { MANUAL | LIMITED | AUTO }
  • Enable/Disable (a) automatic degree of parallelism, (b) statement queueing, and (c) in-memory parallel execution
  • MANUAL => Default. Revert to behavior prior to 11g. No parallelism automatically enabled
  • LIMITED => Enables automatic degree of parallelism for some stmts only.
  • AUTO => all three enabled.


(Q) How does the optimizer determines the degree of parallelism for a statement?

  • Based on the resource requirements of the statement.
  • Limit on parallelization is set by
    • (a) PARALLEL_DEGREE_LIMIT (default = PARALLEL_THREADS_PER_CPU * CPU_COUNT * num instances)
    • (b) PARALLEL_MIN_TIME_THRESHOLD (default = 10sec
(Q) Which parameters affect the automatic degree of parallelism adopted in the system?
  • PARALLEL_DEGREE_POLICY = { MANUAL | LIMITED | AUTO }
  • PARALLEL_DEGREE_LIMIT
  • PARALLEL_MIN_TIME_THRESHOLD
==> The default degree of parallelism is MANUAL

SQL> show parameter parallel

NAME                           TYPE        VALUE
------------------------------ ----------- -------------
..
parallel_degree_limit           string      CPU
parallel_degree_policy      string      MANUAL    
...
parallel_min_time_threshold    string      AUTO
...

SQL> alter system set parallel_degree_policy=auto;
System altered.

SQL> conn sh/sh
Connected.

SQL> select degree from user_tables where table_name ='SALES';

DEGREE
----------
  1

SQL> alter table sales parallel 4;
Table altered.

SQL> select degree from user_tables where table_name ='SALES';

DEGREE
----------
  4

SQL> explain plan for select * from sales;
Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------------
Plan hash value: 1550251865

---------------------------------------------------------------------------------------------
| Id  | Operation     | Name  | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |       | 918K|  25M| 526   (9)| 00:00:07 |     |     |
|   1 |  PARTITION RANGE ALL|       | 918K|  25M| 526   (9)| 00:00:07 |   1 |  28 |
|   2 |   TABLE ACCESS FULL | SALES | 918K|  25M| 526   (9)| 00:00:07 |   1 |  28 |
---------------------------------------------------------------------------------------------
Note
PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------------
   - automatic DOP: Computed Degree of Parallelism is 1 because of parallel threshold
13 rows selected.

==> Note here that no parallel execution took plase. 
    The estimated serial execution time (7sec) is still below the 10sec threshold used when the parallel_min_time_threshold is set to AUTO.  


==> Now, lets change the threshold to 1sec:

SQL> conn / as sysdba
Connected.
SQL> show parameter parallel

NAME                           TYPE        VALUE
------------------------------ ----------- -------------
..
parallel_degree_limit           string      CPU
parallel_degree_policy      string      MANUAL    
...
parallel_min_time_threshold    string      AUTO
...

SQL> alter system set parallel_min_time_threshold=1;
System altered.

SQL> show parameter parallel_min
NAME                             TYPE        VALUE
-------------------------------- ----------- -------
parallel_min_time_threshold      string      1

SQL> Conn sh/sh
Connected
SQL> explain plan for select * from sales;
Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 3060979429

------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation            | Name     | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |    TQ  |IN-OUT| PQ Distrib |
------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |          |   918K|    25M|   291   (9)| 00:00:04 |       |       |        |      |            |
|   1 |  PX COORDINATOR      |          |       |       |            |          |       |       |        |      |            |
|   2 |   PX SEND QC (RANDOM)| :TQ10000 |   918K|    25M|   291   (9)| 00:00:04 |       |       |  Q1,00 | P->S | QC (RAND)  |
|   3 |    PX BLOCK ITERATOR |          |   918K|    25M|   291   (9)| 00:00:04 |     1 |    28 |  Q1,00 | PCWC |            |
|   4 |     TABLE ACCESS FULL| SALES    |   918K|    25M|   291   (9)| 00:00:04 |     1 |    28 |  Q1,00 | PCWP |            |
------------------------------------------------------------------------------------------------------------------------------

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------
Note
-----
   - automatic DOP: Computed Degree of Parallelism is 2
15 rows selected.


(ref) On Transactions, Locks and Cursors

Oracle Locking mechanisms

  • locks prevent destructive interactions that incorrectly update data or incorrectly alter underlying data structures, between transactions accessing shared data.
  • Two types: exclusive locks and share locks.
  • Oracle's default locking mechanisms ensures data concurrency, data integrity, and statement-level read consistency.
    • A row is locked only when modified by a writer.
    • A writer of a row blocks a concurrent writer of the same row.
    • A reader never blocks a writer.(except: SELECT .. FOR UPDATE)
    • A writer never blocks a reader.


Lock modes: level of restrictiveness x degree of data concurrency
  • The less restrictive the level, the more available the data is for access by other users.
  • Exclusive lock mode:
    • Prevents the resource from being shared. Obtained for data modification.
    • The first transaction to lock a resource exclusively is the only transaction that can alter the resource until the exclusive lock is released.
  • Share lock mode:
    • Allows resource to be shared. Multiple users reading data can share the data, holding share locks to prevent concurrent access by a writer who needs an exclusive lock.
    • Several transactions can acquire share locks on the same resource.
    • If a SELECT ... FOR UPDATE selects a single table row, the transaction acquires (a) an exclusive row lock and (b) a row share table lock.
    • The row lock allows other sessions to modify any rows other than the locked row, while the table lock prevents sessions from altering the structure of the table.
    • Oracle Database never escalates locks


DML_LOCKS (init.ora)

Default: Derived: 4 * TRANSACTIONS (assumes an average of 4 tables referenced for each transaction)
Modifiable: No
Range: 20 to unlimited


Automatic Locks
  • DML Lock
    • also called data lock. Guarantees integrity of data concurrently accessed by multiple users.
    • They are either Row Locks (TX) or Table Locks (TM).
    • DML_LOCKS (init.ora): determines the maximum number of DML locks (one for each table modified in a transaction). You might need to increase it if you use explicit locks.
  • DDL Locks
    • Protects the definition of a schema object while an ongoing DDL operation acts on or refers to the object.
    • Users cannot explicitly request DDL locks.
    • Exclusive DDL lock: prevents other sessions from obtaining a DDL or DML lock.
    • Share DDL lock: prevents conflicting DDL operations, but allows similar DDL operations.
  • Breakable Parse Locks
    • Held by a SQL statement or PL/SQL program unit for each schema object that it references. Acquired so that the associated shared SQL area can be invalidated if a referenced object is altered or dropped.
    • A parse lock is acquired in the shared pool during the parse phase of SQL statement execution. The lock is held as long as the shared SQL area for that statement remains in the shared pool.

DML Locks
ROW LOCK (TX)
  • If a transaction obtains a lock for a row, then the transaction also acquires a lock for the table containing the row. (To prevent conflicting DDL operations).
  • Oracle stores lock information in the data block that contains the locked row.
TABLE LOCK (TM)
  • acquired when a table is modified by an I,U,D, MERGE, SELECT FOR UPDATE, or LOCK TABLE statement. TM can be held as:
  • Row Share (RS): (subshare table lock (SS)), indicates that the transaction holding the lock on the table has locked rows in the table and intends to update them. It is the least restrictive mode of table lock, offering the highest degree of concurrency for a table.
  • Row Exclusive Table Lock (RX): (subexclusive table lock (SX)), indicates that the transaction holding the lock has updated table rows or issued SELECT ... FOR UPDATE. It An allows other transactions to query, insert, update, delete, or lock rows concurrently in the same table.
  • Share Table Lock (S): allows other transactions to query the table (without using SELECT ... FOR UPDATE), but updates are allowed only if a single transaction holds the share table lock.
  • Share Row Exclusive Table Lock (SRX): (share-subexclusive table lock (SSX)). More restrictive than a share table lock. Only one transaction at a time can acquire an SSX lock on a given table. Allows other transactions to query (except for SELECT ... FOR UPDATE) but not to update the table.
  • Exclusive Table Lock (X): The most restrictive. Prohibits other transactions from performing any type of DML statement or placing any type of lock on the table.


  • System Locks: Latches, Mutexes, and Internal locks
  • Latches:
    • Serializes access to memory structures. Protect shared memory resources from corruption when accessed by multiple processes. [ (a) Concurrent modification by multiple sessions; (b) Being read by one session while being modified by another session; (c) Deallocation (aging out) of memory while being accessed. ]
    • i.e. while processing a salary update of a single employee, the database may obtain and release thousands of latches
    • Increase in latching ==> decrease in concurrency. (i.e.: excessive hard parse operations create contention for the library cache latch.)
    • V$LATCH: contains detailed latch usage statistics for each latch, including the number of times each latch was requested and waited for.
  • Mutex (Mutual exclusion object)
    • similar to a latch, but whereas a latch typically protects a group of objects, a mutex protects a single object.
  • Internal Locks
    • Dictionary cache locks, file and log mgmt locks, Tablespace and Undo segment locks)







Manual data Locks: overriding default
  • LOCK TABLE
  • SELECT FOR UPDATE clause
  • SET TRANSACTION with the READ ONLY or ISOLATION LEVEL SERIALIZABLE option


On Isolation Levels
ALTER SESSION SET ISOLATION_LEVEL = {SERIALIZABLE | READ COMMITTED | READ ONLY}

  • The ISOLATION_LEVEL parameter specifies how transactions containing database modifications are handled.
  • It is a session parameter only, NOT an initialization parameter.
  • SERIALIZABLE:
    • transaction sees only changes committed at the time the transaction—not the query—began and changes made by the transaction itself.
    • Transactions in the session use the serializable transaction isolation mode as specified in the SQL standard.
    • If a serializable transaction attempts to execute a DML statement that updates rows currently being updated by another uncommitted transaction at the start of the serializable transaction, then the DML statement fails.
    • A serializable transaction can see its own updates.
  • READ COMMITTED: (default)
    • Transactions in the session will use the default Oracle Database transaction behavior.
    • every query executed by a transaction sees only data committed before the query—not the transaction—began.
    • phantoms and fuzzy reads: because the database does not prevent other transactions from modifying data read by a query, other transactions may change data between query executions. Thus, a transaction that runs the same query twice may experience fuzzy reads and phantoms.
    • If the transaction contains DML that requires row locks held by another transaction, then the DML statement will wait until the row locks are released.


LOCK TABLE
  • explicitly locks one or more tables in a specified lock mode.
  • The lock mode determines what other locks can be placed on the table.
  • A table lock never prevents other users from querying a table, and a query never acquires a table lock
  • When a LOCK TABLE statement is issued on a view, the underlying base tables are locked
  • LOCK TABLE employees, departments IN EXCLUSIVE MODE NOWAIT;
  • MODE: [ NOWAIT | WAIT n | ]
    • NOWAIT: error if lock is not available immediately
    • WAIT n: wait up to n secs
    • <blank>: wait indefinitely to acquire the lock

what type of lock to use (check here)
  • ROW SHARE and ROW EXCLUSIVE
  • SHARE MODE
  • SHARE ROW EXCLUSIVE
  • EXCLUSIVE MODE
  • Let Oracle do the locking:
  • [ SET TRANSACTION ISOLATION LEVEL level ] or
    [ ALTER SESSION ISOLATION LEVEL level ]



















Transactions, TCL, and Isolation Levels
..later..












Autonomous transactions



Using SELECT... FOR UPDATE
  • SELECT FOR UPDATE selects the rows of the result set and locks them.
  • Enables you to base an update on the existing values in the rows, because it ensures that no other user can change those values before you update them.
  • You can also use SELECT FOR UPDATE to lock rows that you do not want to update
  • By default, SELECT FOR UPDATE waits until the requested row lock is acquired. To change this behavior, use the [ NOWAIT | WAIT | SKIP LOCKED ].
  • When SELECT FOR UPDATE is associated with an explicit cursor, the cursor is called a FOR UPDATE cursor.

set serveroutput on
declare
 my_emp_ln employees.last_name%type;
 my_emp_id number;
 my_job_id varchar2(10);
 my_sal number(8,2);
 newsal number(8,2);
 
 -- declare a FOR UPDATE cursor
 cursor c1 is
   select employee_id, last_name, job_id, salary
   from employees for update;
begin
  open c1;
  loop
    fetch c1 into my_emp_id, my_emp_ln, my_job_id, my_sal;
    If my_job_id = 'SA_REP' then
      newsal := my_sal*1.12;
      -- update only the rows locked by the cursor
      -- identified through the "WHERE CURRENT OF" clause.
      update employees
      set salary = newsal
      where current of c1;
      dbms_output.put_line('Emp '|| my_emp_ln ||
               ': salary increased from '|| my_sal ||' to '|| newsal);
    end if;
    exit when c1%notfound;
  end loop;
end;

anonymous block completed
Emp Tucker: salary increased from 10000 to 11200
Emp Bernstein: salary increased from 9500 to 10640
...