Foxhound is the better* Database Monitor for SQL Anywhere.
*better: More thorough, more relevant, more effective.
...more Alerts, more All Clears, more details, more control in your hands.


[Home] [Table of Contents]

Breck Carter
Last modified: February 12, 1998
mail to: bcarter@bcarter.com


Tip 77: Sybase SQL Anywhere
Performance Tips & Techniques

Overview

Introduction

"Performance is your reality.
Forget everything else."

"Optimization is not some mystical state of grace,
it is an intricate act of human labor which carries
real costs and real risks."

The Truth... SSA runs well out of the box.

The Myth... You cannot tune SQL Anywhere.

Yes, you can make improvements

1 - Can SQL Anywhere Handle Your Load?

It depends...

2 - Use The Latest Version

5.5.03 Build #1666

5.5.01

5.5.02

5.5.03

3 - Learn To Read The Plan

     SELECT * FROM child1, parent1

      WHERE child1.primary1 = parent1.primary1
        AND parent1.primary1 = 44

     PLAN> parent1 (parent1), child1 (parent1_child1)

     table-name (seq)            table scan (bad)
     table-name (table-name)     primary key index (good)
     table-name (role-name)      foreign key index (good)
     table-name (index-name)     index (good)
     TEMPORARY TABLE table-name  temp table (bad)
     table-name (table-name      constraint (good)
          UNIQUE (column-name))
     table-name (seq):           nested, UNION'ed queries
          table-name (seq)

4 - Run ISQL In Batch Mode

ISQL -c "UID=DBA;PWD=SQL" c:\test\test.sql Input c:\test\test.sql

     CREATE INDEX iquery1 ON query1
        ( search1, search2 );

     SELECT * FROM query1
      WHERE search1 = 44 >&c:\test\isql.txt;

     SELECT * FROM query1
      WHERE search2 = 44 >>&c:\test\isql.txt;
Output >&c:\test\isql.txt
     % Executing command:
     % SELECT * FROM query1
     %     WHERE search1 = 44;
     % Estimated 1 rows in query
           (I/O estimate 2)
     % PLAN> query1 (iquery1)
     44,44,44,44
     % 1 record(s) selected -- actual I/O 0
     % select time including I/O 0.0 seconds

     % Executing command:
     % SELECT * FROM query1
     %     WHERE search2 = 44;
     % Estimated 3 rows in query
           (I/O estimate 404)
     % PLAN> query1 (seq)
     44,44,44,44
     % 1 record(s) selected -- actual I/O 1
     % select time including I/O 0.8 seconds

5 - The Prime Directive: Increase The Cache

Suggestion Number 1: "Make the cache bigger."

Number 2: "Buy more RAM if you have to."

Rules of Thumb:

The Effect Of A Larger Cache
     DBSRV50 -c 400K -xNamedPipes c:\test\tiny.db
     INSERT INTO test2 SELECT * FROM test1;
     I/O 9504, time 82.74 seconds

     DBSRV50 -c 4096K -xNamedPipes c:\test\tiny.db
     INSERT INTO test2 SELECT * FROM test1;
     I/O 682, time 2.4 seconds

6 - Use A Large Page Size

          DBINIT -p 4096 c:\test\big.db
          DBINIT -p 2048 c:\test\small.db
          DBSRV50 -xNamedPipes -gp 4096 -nTesting c:\test\small.db -nSmall

7 - Use A Log File

          FOR ll_primary1 = 1 TO 100
             UPDATE test1 SET search1 = search1 + 1
              WHERE primary1 = :ll_primary1 USING SQLCA;
             IF SQLCA.SQLCode <> 0 THEN... error
             COMMIT USING SQLCA;
             IF SQLCA.SQLCode <> 0 THEN... error
          NEXT

8 - Think Sets

Shorter, Faster, Easier

Two tenths of a second...

     UPDATE test1
        SET salary = salary + 10000
      WHERE dept_id = 5 USING SQLCA;
     COMMIT...

8 seconds...

     DECLARE CURSOR FOR SELECT
     OPEN
     FETCH INTO
     DO WHILE
        UPDATE WHERE CURRENT OF
        FETCH INTO
     LOOP
     COMMIT

9 - Create Indexes

10 - Understand Index Column Placement

     CREATE INDEX iquery1 ON query1( search1, search2 );

     SELECT * FROM query1 WHERE search1 = 44;
        Estimated 1 rows in query (I/O estimate 2)
        PLAN> query1 (iquery1)

     SELECT * FROM query1 WHERE search1 = 44
                            AND search2 = 44;
        Estimated 1 rows in query (I/O estimate 2)
        PLAN> query1 (iquery1)

     SELECT * FROM query1 WHERE search2 = 44;
        Estimated 3 rows in query (I/O estimate 220)
        PLAN> query1 (seq)

     CREATE INDEX iquery2 ON query1 ( search2 );

     SELECT * FROM query1 WHERE search2 = 44;
        Estimated 1 rows in query (I/O estimate 2)
        PLAN> query1 (iquery2)

11 - Always Define A Primary Key

(or a UNIQUE NOT NULL index)
(or a UNIQUE constraint)

Otherwise...

12 - Trust Your Keys

13 - Understand Primary Key Column Placement

          CREATE TABLE parent1
             ( primary2 INTEGER NOT NULL,
               primary1 INTEGER NOT NULL,
               data1 INTEGER NOT NULL,
               PRIMARY KEY ( primary1, primary2 ) );

          SELECT * FROM parent1 WHERE primary1 = 44;
             Estimated 3 rows in query (I/O estimate 182)
             PLAN> parent1 (seq)

          SELECT * FROM parent1 WHERE primary2 = 44;
             Estimated 8 rows in query (I/O estimate 2)
             PLAN> parent1 (parent1)

14 - Understand ASC And DESC

Will this index help an ORDER BY?

15 - Consider An Explicit Primary Key Index

16 - Use Indexes for Max() and Min()

     CREATE INDEX iquery2 ON query1 ( search2 );
     SELECT Max ( search1 ) FROM query1;
        PLAN> query1 (seq)
        Estimated - rows 1, I/O 626
        Actual    - rows 1, I/O 347, time 1.91 seconds
     SELECT Max ( search2 ) FROM query1;
        PLAN> query1 (iquery2)
        Estimated - rows 1, I/O 7222
        Actual    - rows 1, I/O 6, time 0.10 seconds

Your mileage may vary

17 - Use LIKE 'xx%'

          SELECT * FROM query1 WHERE search1 LIKE '5.%';
             Estimated 1 rows in query ( I/O estimate 2)
             PLAN> query1 (iquery1)
          SELECT * FROM query1 WHERE Left ( search1, 2 ) = '5.';
             Estimated 500 rows in query ( I/O estimate 200)
             PLAN> query1 (seq)

18 - Use EXISTS Instead Of COUNT(*)

"Does this exist?"

     SELECT COUNT(*) INTO :ll_count FROM xref_info
      WHERE short_event LIKE '%open%'

     SELECT IF EXISTS (
               SELECT short_event
                 FROM xref_info
                WHERE short_event like '%open%' )
            THEN 1
            ELSE 0
            ENDIF
       INTO :ll_yes_no FROM SYS.DUMMY

"Are There Any Rows At All?"

     SELECT COUNT(*) FROM test1;
        PLAN> test1 (seq)
        I/O 256, time 0.87 seconds

     SELECT IF EXISTS ( SELECT * FROM test1 )
            THEN 1
            ELSE 0
            ENDIF;
        PLAN> DUMMY (seq): test1 (seq)
        I/O 1, time 0.5 seconds

19 - Use UNION ALL

     SELECT * FROM part1
     UNION
     SELECT * FROM part2;
        PLAN> TEMPORARY TABLE part1 (seq): part2 (seq)
                Estim. - rows 2000, I/O 2695
        Actual - rows 2000, I/O 401, time 1.80 seconds

     SELECT * FROM part1
     UNION ALL
     SELECT * FROM part2;
        PLAN> part1 (seq): part2 (seq)
            Estim. - rows 2000, I/O 94
        Actual - rows 2000, I/O 50, time 1.10 seconds

20 - Replace OR With UNION

OR may do slow sequential scan

     SELECT * FROM indexed1
      WHERE search1 = 4444
         OR search2 = 5555;
     PLAN> indexed1 (seq)
     Estimated: I/O 626
     Actual:    I/O 358, time 2.95 seconds

UNION may use multiple indexes

     SELECT * FROM indexed1
         WHERE search1 = 4444
        UNION
        SELECT * FROM indexed1
         WHERE search2 = 5555;
     PLAN> TEMPORARY TABLE indexed1 (isearch1):
                           indexed1 (isearch2)
     Estimated: I/O 9
     Actual:    I/O 10, time 0.21 seconds

21 - Be Reasonable With The Indexes

UPDATE Slowed by Multiple Indexes

     CREATE INDEX is01 ON multi1 ( s01 );
     ...
     CREATE INDEX is20 ON multi1 ( s20 );

     UPDATE multi1 SET
        s01=s01+1, s02=s02+1, s03=s03+1,
        s04=s04+1, s05=s05+1, s06=s06+1,
        s07=s07+1, s08=s08+1, s09=s09+1,
        s10=s10+1, s11=s11+1, s12=s12+1,
        s13=s13+1, s14=s14+1, s15=s15+1,
        s16=s16+1, s17=s17+1, s18=s18+1,
        s19=s19+1, s20=s20+1;

     UPDATE 20 Columns          I/O  Seconds
     -----------------         ----  -------
     Just a primary key        1436    9.35
     After 2 indexes created   1666   11.62
     After 6 indexes created   2126   15.55
     After 20 indexes created  4302   54.46

22 - Pick The Right Time To Create An Index

23 - Watch Out For Wide Indexes

24 - Use The SQL Central Performance Monitor

25 - Use The NT Performance Monitor

26 - Diagnose Problems With Runtime Properties

     SELECT connection_property ( 'CommLink' ) as CommLink,
            db_property ( 'ConnCount' ) as ConnCount,
            property ( 'DiskRead' ) as DiskRead,
            property ( 'DiskWrite' ) as DiskWrite
       INTO :ls_CommLink,
            :ll_ConnCount,
            :ll_DiskRead,
            :ll_DiskWrite
       FROM sys.dummy USING SQLCA;
     IF SQLCA.SQLCode <> 0 THEN ...error

     MessageBox ( "Properties", &
        "CommLink:~t" + ls_CommLink + "~r~n" &
      + "ConnCount:~t" + string ( ll_ConnCount ) + "~r~n" &
      + "DiskRead:~t" + string ( ll_DiskRead ) + "~r~n" &
      + "DiskWrite:~t" + string ( ll_DiskWrite ) )

27 - Find Blocked Connections With System Functions

  1. xx = Next_Connection ( NULL )
  2. yy = Connection_Property ( 'BlockedOn', xx )
  3. uxx = Connection_Property ( 'UserID', xx )
  4. uyy = Connection_Property ( 'UserID', yy ).
  5. xx = Next_Connection ( xx )
  6. Repeat from Step 2 until xx is empty
  7. Display blocked connections if any

28 - Understand DEFAULT AUTOINCREMENT

29 - Watch Out For Correlated Subqueries

Correlated subqueries aren't always bad

30 - Beware Of Slow DDL

31 - Deal With Disk Fragmentation

32 - Use Stored Procedures And Triggers

33 - Limit Result Sets With SET ROWCOUNT

          ls_SQL = "SET ROWCOUNT 100"
          EXECUTE IMMEDIATE :ls_SQL
            USING SQLCA;
          IF SQLCA.SQLCode <> 0 THEN...error

34 - Interrupt Processing With SET ROWCOUNT

35 - Put Different Data On Different Drives

36 - Monitor Client And Server With DBWatch

37 - Use -x To Limit Protocols

40 seconds longer startup without -x ...

38 - Use TCP/IP

A Final Word

"I think it is an immutable law in business that
words are words, explanations are explanations,
promises are promises
- but only performance is reality."

"Optimization is not free.
Gratuitous optimization can be translated directly
into missing features or later release dates."

"Knowing when to optimize
is as important as knowing how."


[Home] [Table of Contents]