Thursday, May 21, 2026

Database Performance Tuning with Indexes: What Most Developers Get Wrong

When performance problems appear in a database, the first reaction is often:

“Let’s add an index.”

Sometimes that works. Sometimes it changes nothing. And in some cases, it even makes the query slower.

The problem is not indexes themselves — it’s the misunderstanding of how indexes actually work.

This article explains database indexing in a practical and beginner-friendly way using simple SQL examples. Instead of memorizing rules, you’ll understand why indexes help, when they fail, and how to design them correctly.


The Example Table

Let’s start with a simple orders table containing hundreds of thousands of rows.

CREATE TABLE orders
(
    id                BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    firstName         VARCHAR(255) NOT NULL,
    lastName          VARCHAR(255) NOT NULL,
    address           VARCHAR(255) NOT NULL,
    status            ENUM ('created', 'preparing', 'prepared', 'shipped', 'delivered') NOT NULL,
    canPreparingStart TINYINT(1) NOT NULL,
    created_at        TIMESTAMP NULL,
    updated_at        TIMESTAMP NULL,
    deleted_at        TIMESTAMP NULL
)
COLLATE = utf8mb4_unicode_ci;

Now imagine the application frequently runs this query:

SELECT * 
FROM orders 
WHERE firstName = 'Pavel';

At first glance, the query looks harmless.

But with hundreds of thousands (or millions) of rows, performance starts becoming a problem.


Understanding the Problem with EXPLAIN

Before optimizing queries, we should understand how the database executes them.

That’s where EXPLAIN becomes useful.

EXPLAIN
SELECT * 
FROM orders 
WHERE firstName = 'Pavel';

Without an index, the database performs a full table scan.

That means:

  • Every row is read

  • Every row is checked

  • The database scans the entire table

Even if only a few rows match.

This is expensive.


What Is an Index?

An index helps the database find rows faster without scanning the entire table.

The most common type is the B-Tree index.

Think of it like a phone book.

If you want to find someone named “Pavel”:

  • You don’t start reading from page 1

  • You jump somewhere near the middle

  • Then narrow the search step by step

That’s essentially how a B-Tree index works.


Adding the First Index

Let’s create an index on firstName.

CREATE INDEX orders_firstName_index 
ON orders (firstName);

Now run the same query again:

SELECT * 
FROM orders 
WHERE firstName = 'Pavel';

Performance improves dramatically because the database now performs an:

  • Index lookup
    instead of a

  • Full table scan

The index allows the database to jump directly to matching rows.


B-Tree Indexes Work Left to Right

At this point, many developers think:

“Great. Indexes solved the problem.”

But here comes the first important limitation.

Consider this query:

SELECT * 
FROM orders 
WHERE firstName LIKE '%vel';

Or:

SELECT * 
FROM orders 
WHERE firstName LIKE '%ave%';

Even with the index present, the database often ignores it and performs a full table scan.

Why?

Because B-Tree indexes work from left to right.


Why Leading Wildcards Break Indexes

A B-Tree index stores data in sorted order.

For example:

Adam
Alex
Daniel
Pavel
Peter

The database can efficiently search:

LIKE 'Pa%'

because it knows where names starting with “Pa” begin.

But with:

LIKE '%vel'

there’s no starting point.

The database has no choice but to scan everything.


What B-Tree Indexes Handle Well

B-Tree indexes are excellent for:

Exact matches

WHERE firstName = 'Pavel'

Prefix searches

WHERE firstName LIKE 'Pa%'

Range queries

WHERE created_at BETWEEN '2025-01-01' AND '2025-12-31'

What B-Tree Indexes Do NOT Handle Well

B-Tree indexes struggle with:

Suffix searches

LIKE '%text'

Contains searches

LIKE '%text%'

Functions on columns

WHERE LOWER(firstName) = 'pavel'

Different Queries Need Different Indexes

This does not mean those queries cannot be optimized.

It simply means they require different index types, such as:

  • Full-text indexes

  • Functional indexes

  • Trigram indexes

The key lesson:

B-Tree indexes are powerful, but they are not universal solutions.


The Second Limitation: Data Distribution

Now let’s look at another example.

Imagine this query:

SELECT *
FROM orders
WHERE deleted_at IS NULL
LIMIT 20000 OFFSET 0;

A common assumption is:

“Let’s index deleted_at.”

So we do:

CREATE INDEX orders_deleted_at_index
ON orders (deleted_at);

Surprisingly, performance may actually become worse.

Why?


The Data Matters More Than the Index

Suppose almost every row has:

deleted_at = NULL

This means the column has low selectivity.

In simple terms:

  • The index cannot effectively narrow down results

  • Too many rows match the condition

The database still has to process a huge amount of data.


Phone Book Analogy Again

Imagine a phone book where almost everyone has the same name.

Even though the book is sorted, searching becomes inefficient because the result set is massive.

That’s exactly what happens with low-selectivity indexes.


High Selectivity Makes Indexes Powerful

Now imagine we soft-delete most records.

Suddenly:

WHERE deleted_at IS NULL

matches only a small percentage of rows.

Now the index becomes extremely useful because it can quickly isolate a tiny subset of records.

This is why:

Good indexing is not only about queries — it’s also about data distribution.


Composite Indexes

Now let’s move to composite indexes.

Suppose we frequently run:

SELECT *
FROM orders
WHERE firstName = 'Pavel'
AND lastName = 'Komin';

A composite index can help significantly.

CREATE INDEX orders_first_last_index
ON orders (firstName, lastName);

This is usually much better than creating two separate indexes.


Why Composite Indexes Work Better

With separate indexes:

INDEX(firstName)
INDEX(lastName)

the database may still need to combine results internally.

But with a composite index:

(firstName, lastName)

the database can directly navigate to the exact combination.

This reduces work dramatically.


Column Order Is Critical

Composite indexes still follow the same left-to-right rule.

So this index:

(firstName, lastName)

works well for:

WHERE firstName = 'Pavel'

and:

WHERE firstName = 'Pavel'
AND lastName = 'Komin'

But not efficiently for:

WHERE lastName = 'Komin'

because the search starts from the second column, breaking the left-to-right chain.


Choosing Column Order Correctly

The first column in a composite index should ideally be:

  • Frequently queried

  • Highly selective

This allows the database to eliminate as many rows as possible early in the search process.


The Biggest Indexing Mistake

One of the most common mistakes is creating indexes without understanding:

  • Query patterns

  • Data distribution

  • Selectivity

  • Index structure

Indexes are not magic.

Poorly designed indexes can:

  • Waste storage

  • Slow down inserts and updates

  • Increase maintenance overhead

  • Make queries slower


Key Takeaways

1. B-Tree indexes work left to right

They are optimized for:

  • Exact matches

  • Prefix searches

  • Range queries


2. Leading wildcards break B-Tree indexes

These queries are problematic:

LIKE '%text'
LIKE '%text%'

3. Data selectivity matters

Indexes on low-diversity columns often provide little benefit.


4. Composite indexes depend on column order

The first column is the most important.


5. Good indexing requires understanding your workload

Always analyze:

  • Query frequency

  • Filtering patterns

  • Cardinality/selectivity

  • Execution plans (EXPLAIN)

before adding indexes.


Final Thoughts

Indexes are one of the most powerful tools for database optimization — but only when used correctly.

Understanding:

  • how B-Tree indexes work,

  • why selectivity matters,

  • and how composite indexes behave

will help you optimize databases far more effectively than simply “adding indexes everywhere.”

The best database engineers don’t just create indexes.

They understand why the database chooses to use them — or ignore them.

Friday, May 15, 2026

Oracle Database Creation Using Manual Method (Step-by-Step)

 


Prerequisites Before Creating Database

a) Oracle software must be installed and ORACLE_HOME should be configured.

b) Set environment variables:

export ORACLE_BASE=/u01/app/oracle
export ORACLE_HOME=/u01/app/oracle/product/19.3.0/dbhome_1
export ORACLE_SID=ORCL
export PATH=$ORACLE_HOME/bin:$PATH

c) Create required directories:

mkdir -p /u01/app/oracle/oradata/ORCL
mkdir -p /u01/app/oracle/fast_recovery_area/ORCL
mkdir -p /u01/app/oracle/admin/ORCL/adump

Step 1 – Create Password File

Password file stores SYS user password for remote SYSDBA connections.

Command:

orapwd file=$ORACLE_HOME/dbs/orapwORCL password=Oracle123 entries=10

Step 2 – Create PFILE (init.ora)

Create initialization parameter file.

Example:

db_name='ORCL'
memory_target=2G
processes=300
audit_file_dest='/u01/app/oracle/admin/ORCL/adump'
db_recovery_file_dest='/u01/app/oracle/fast_recovery_area'
db_recovery_file_dest_size=10G
control_files='/u01/app/oracle/oradata/ORCL/control01.ctl'
compatible='19.0.0'

Step 3 – Start Database in NOMOUNT Mode

Start Oracle instance using PFILE.

SQL> startup nomount pfile='$ORACLE_HOME/dbs/initORCL.ora';

Verify:
SQL> show parameter memory_target;

Step 4 – Run CREATE DATABASE Command

This step creates the database files, redo logs, undo tablespace, SYSTEM/SYSAUX tablespaces.

Example:

CREATE DATABASE ORCL
USER SYS IDENTIFIED BY Oracle123
USER SYSTEM IDENTIFIED BY Oracle123
LOGFILE GROUP 1 ('/u01/app/oracle/oradata/ORCL/redo01.log') SIZE 100M,
GROUP 2 ('/u01/app/oracle/oradata/ORCL/redo02.log') SIZE 100M,
GROUP 3 ('/u01/app/oracle/oradata/ORCL/redo03.log') SIZE 100M
MAXLOGFILES 16
MAXDATAFILES 200
CHARACTER SET AL32UTF8
NATIONAL CHARACTER SET AL16UTF16
DATAFILE '/u01/app/oracle/oradata/ORCL/system01.dbf' SIZE 700M REUSE
SYSAUX DATAFILE '/u01/app/oracle/oradata/ORCL/sysaux01.dbf' SIZE 500M REUSE
DEFAULT TABLESPACE users
DATAFILE '/u01/app/oracle/oradata/ORCL/users01.dbf' SIZE 100M
UNDO TABLESPACE undotbs1
DATAFILE '/u01/app/oracle/oradata/ORCL/undotbs01.dbf' SIZE 200M;

Step 5 – Run Catalog and Catproc Scripts

Execute Oracle dictionary and PL/SQL package creation scripts.

SQL> @?/rdbms/admin/catalog.sql
SQL> @?/rdbms/admin/catproc.sql

Step 6 – Create SPFILE from PFILE

Convert PFILE to SPFILE.

SQL> create spfile from pfile;

Restart database:

SQL> shutdown immediate;
SQL> startup;

Step 7 – Configure Listener

Edit listener.ora and start listener.

lsnrctl start
lsnrctl status

Step 8 – Configure tnsnames.ora

Example tnsnames entry:

ORCL=
 (DESCRIPTION=
   (ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))
   (CONNECT_DATA=
      (SERVICE_NAME=ORCL)
   )
 )

Step 9 – Final Verification

Verify database status:

SQL> SELECT NAME, OPEN_MODE, DB_UNIQUE_NAME FROM V$DATABASE;

Check tablespaces:

SQL> SELECT TABLESPACE_NAME, STATUS FROM DBA_TABLESPACES;

Check listener:

lsnrctl status

Best Practices & DBA Tips

- Always verify ORACLE_SID before starting database.
- Use AL32UTF8 character set for multilingual support.
- Keep control files multiplexed.
- Take RMAN backup immediately after database creation.
- Configure archive log mode for production databases.
- Monitor alert logs after startup.

Sunday, May 10, 2026

Load Balancer Setup for 3-Node Oracle RAC Database

 


Load Balancer Setup for 3-Node Oracle RAC Database

1. Objective

This document explains the Load Balancer setup and network architecture for a 3-node Oracle RAC environment. It covers:

  • RAC network flow
  • SCAN configuration
  • VIP configuration
  • Load balancer options
  • Recommended architecture
  • DNS and listener setup
  • Best practices

2. Oracle RAC Network Architecture

A 3-node Oracle RAC typically contains:

Component

Purpose

Public IP

Node communication with clients

Private IP

Interconnect for Cache Fusion

VIP (Virtual IP)

Fast failover for node failures

SCAN IP

Client connection abstraction

Listener

Accepts database connections


3. Example Network Layout

Node

Hostname

Public IP

VIP IP

Private IP

Node1

rac1

192.168.1.101

192.168.1.111

10.10.10.1

Node2

rac2

192.168.1.102

192.168.1.112

10.10.10.2

Node3

rac3

192.168.1.103

192.168.1.113

10.10.10.3


4. SCAN (Single Client Access Name)

Oracle recommends SCAN for all RAC environments.

SCAN Requirements

  • Minimum 3 SCAN IPs
  • One SCAN Name
  • Round Robin DNS entry

Example:

SCAN Name

SCAN IPs

rac-scan.company.com

192.168.1.120

192.168.1.121

192.168.1.122


5. DNS Configuration

Example DNS entries:

rac1.company.com        192.168.1.101

rac2.company.com        192.168.1.102

rac3.company.com        192.168.1.103

 

rac1-vip.company.com    192.168.1.111

rac2-vip.company.com    192.168.1.112

rac3-vip.company.com    192.168.1.113

 

rac-scan.company.com    192.168.1.120

rac-scan.company.com    192.168.1.121

rac-scan.company.com    192.168.1.122

Verify:

nslookup rac-scan.company.com

Expected:

  • All 3 SCAN IPs returned

6. How Load Balancing Works in RAC

Oracle RAC provides built-in load balancing using:

A. SCAN Listener

Distributes connections across:

  • Node listeners
  • RAC instances

B. Local Listener

Each node listener handles local instance traffic.

C. Server-side Load Balancing

Oracle automatically redirects clients to least loaded instances.


7. Connection Flow

Application

     |

     v

SCAN Listener

     |

     +-------------------+

     |         |         |

     v         v         v

 Node1      Node2      Node3

 Listener   Listener   Listener

     |         |         |

 Instance1 Instance2 Instance3


8. Do We Need External Load Balancer?

Usually NOT Required

Oracle RAC SCAN already provides:

  • Load balancing
  • Connection failover
  • High availability

9. When External Load Balancer is Needed

Use external LB only when:

Scenario

Need LB?

Web applications

Yes

DR routing

Yes

Multi-site traffic

Yes

SSL offloading

Yes

Non-Oracle clients

Sometimes

Standard RAC DB access

No


10. Recommended Load Balancers

Hardware

  • F5 BIG-IP
  • Citrix ADC
  • A10 Networks

Software

  • HAProxy
  • NGINX
  • LVS

11. F5 Load Balancer Example

Virtual Server

VIP: 192.168.1.200

Port: 1521

Pool Members

192.168.1.111:1521

192.168.1.112:1521

192.168.1.113:1521

Health Check

TCP Port 1521


12. HAProxy Example

Install

yum install haproxy -y

Configuration

frontend oracle_front

    bind *:1521

    mode tcp

    default_backend oracle_backend

 

backend oracle_backend

    mode tcp

    balance roundrobin

 

    server rac1 192.168.1.111:1521 check

    server rac2 192.168.1.112:1521 check

    server rac3 192.168.1.113:1521 check

Restart:

systemctl restart haproxy


13. Oracle Client Connection String

Recommended EZConnect

sqlplus system/password@//rac-scan.company.com:1521/PROD

TNS Example

PRODDB=

 (DESCRIPTION=

   (ADDRESS=(PROTOCOL=TCP)(HOST=rac-scan.company.com)(PORT=1521))

   (CONNECT_DATA=

      (SERVICE_NAME=PROD)

   )

 )


14. Verify SCAN Listener

Check SCAN listeners

srvctl status scan_listener

Check SCAN configuration

srvctl config scan

Listener status

lsnrctl status


15. Best Practices

Network Best Practices

Public Network

  • Use bonded NICs
  • Minimum 1 Gbps
  • Prefer 10 Gbps

Private Interconnect

  • Dedicated NICs
  • Jumbo frames recommended
  • Low latency network

SCAN

  • Always configure 3 SCAN IPs
  • Use DNS instead of /etc/hosts

VIP

  • Separate from public IPs
  • Same subnet as public IP

16. High Availability Flow

Node Failure Scenario

Example:

  • Node2 crashes

Flow:

  1. VIP relocates
  2. Connections fail fast
  3. SCAN redirects new sessions
  4. Surviving nodes continue service

This minimizes application downtime.


17. Important Oracle RAC Ports

Port

Purpose

1521

Listener

6200

ONS

1630

GNS

53

DNS

22

SSH


18. Typical Production Architecture

                +----------------+

                |   Applications |

                +--------+-------+

                         |

                         v

                +----------------+

                | SCAN Listener  |

                +--------+-------+

                         |

         +---------------+---------------+

         |               |               |

         v               v               v

   +-----------+   +-----------+   +-----------+

   |   RAC1    |   |   RAC2    |   |   RAC3    |

   | Listener  |   | Listener  |   | Listener  |

   +-----------+   +-----------+   +-----------+

         |               |               |

         +---------------+---------------+

                         |

                  ASM + Shared Storage


19. Troubleshooting Commands

Check Cluster

crsctl stat res -t

Check VIP

srvctl status vip -n rac1

Check Listener

lsnrctl status LISTENER

Verify Interconnect

oifcfg getif


20. Recommendation

For most Oracle RAC deployments:

✅ Use:

  • SCAN listeners
  • VIPs
  • Oracle native load balancing

❌ Avoid:

  • External LB unless business requires it

Oracle RAC itself is already a highly optimized database load balancing solution.