Comparative Guide to Database Index Types: PostgreSQL, Oracle, and MySQL

Overview of Indexing Mechanisms

When designing robust database architectures, selecting the appropriate index type is crucial for query performance. PostgreSQL, Oracle, and MySQL each offer distinct indexing mechanisms tailored to different data structures and workload profiles.

Common Index Types

  • B-Tree Indexes: Supported across all three platforms, this is the default and most widely used structure. It excels at handling equality checks, range scans, and sorted retrievals.
  • Hash Indexes: Available in PostgreSQL and MySQL (specifically the Memory engine), these provide rapid exact-match lookups but do not support range queries or ordering. Oracle lacks native hash indexes but achieves similar functionality via Hash Clusters.

Database-Specific Index Features

  • PostgreSQL: GIN, GiST, SP-GiST, BRIN, Partial indexes, Expression indexes, Covering indexes (INCLUDE).
  • Oracle: Bitmap, Function-Based, Reverse Key, Domain, Index-Organized Tables (IOT), Partitioned indexes.
  • MySQL: Full-Text, Spatial (R-Tree), Prefix, Invisible indexes.

Feature Comparison Matrix

Platform Index Type Characteristics Optimal Use Case
PostgreSQL B-Tree (Default) Balanced tree structure supporting ranges and equality Primary keys, unique constraints, range conditions (<, >, BETWEEN)
Hash Hash-based storage for exact matches (WAL-secured since v10) Point lookups where range scanning is unnecessary
GIN Generalized Inverted Index for multi-valued elements Array contains, JSONB traversal, full-text search
GiST Extensible framework for custom data structures Geospatial queries (PostGIS), nearest neighbor, range overlaps
BRIN Block Range Index storing summary stats per block Massive append-only tables with natural sorted physical order (e.g., timestamps)
SP-GiST Space-partitioned GiST for non-balanced structures Quad-trees, radix trees, phone number routing
Partial Index conditioned on a subset of rows Indexing only active records, reducing size and maintenance
Expression Index built on a computed result Case-insensitive searches using lower()
Include B-Tree with extra non-key columns in leaf pages Index-only scans without table access (covering queries)
Oracle B-Tree (Default) Standard balanced tree General OLTP lookups, primary/unique keys
Bitmap Bit arrays representing distinct values Low-cardinality columns (status flags, genders) in read-heavy DW environments
Function-Based Index derived from a function evaluation Pre-computed expressions, case-insensitive matching
Reverse Key Reverses byte order of indexed values Preventing right-hand leaf block contention on monotonic sequences
IOT Table data stored within the B-Tree index structure Tables primarily accessed via primary key, minimizing I/O
Partitioned Indexes aligned with table partitions (Local/Global) Large partitioned tables for partition-wise maintenance
MySQL B-Tree (Clustered + Secondary) Clustered index (PK) holds full row; secondary indexes hold PK values Standard OLTP operations; InnoDB default architecture
Hash (Memory Engine) In-memory exact match lookup Session caches, fast equijoins in memory tables
Fulltext Inverted index for text parsing Natural language searches in large text blocks
Spatial (R-Tree) Index for geometric data types GIS applications, geographic distance calculations
Prefix Index on the first N characters of a string Long string fields (URLs, paths) where disk space is a concern
Invisible (8.0+) Index maintained but ignored by the optimizer Safe testing of index removal before actual drop

Practical Implementation Examples

PostgreSQL

-- Standard B-Tree
CREATE INDEX idx_customer_name ON clients(customer_name);

-- Hash for exact match tokens
CREATE INDEX idx_session_token_hash ON sessions USING hash(auth_token);

-- GIN for full-text search
CREATE INDEX idx_manual_content_gin ON manuals USING gin(to_tsvector('english', content));
SELECT * FROM manuals WHERE to_tsvector('english', content) @@ to_tsquery('database');

-- BRIN for naturally ordered time-series
CREATE INDEX idx_metric_time_brin ON metrics USING brin(recorded_at);

-- Expression index for case-insensitive matching
CREATE INDEX idx_account_lower_email ON accounts (lower(email));
SELECT * FROM accounts WHERE lower(email) = 'admin@example.org';

Oracle

-- Standard B-Tree
CREATE INDEX idx_staff_lastname ON staff(lastname);

-- Bitmap for low-cardinality status tracking
CREATE BITMAP INDEX idx_inventory_status ON inventory(availability);

-- Function-Based index
CREATE INDEX idx_client_upper_name ON clients(UPPER(fullname));
SELECT * FROM clients WHERE UPPER(fullname) = 'JOHN DOE';

-- Reverse Key to mitigate index block hotspots
CREATE INDEX idx_order_id_rev ON transactions(order_ref REVERSE);

-- Index-Organized Table (IOT)
CREATE TABLE config_settings (
  setting_id NUMBER PRIMARY KEY,
  config_key VARCHAR2(50),
  config_val VARCHAR2(255)
) ORGANIZATION INDEX;

MySQL

-- Standard B-Tree secondary index
CREATE INDEX idx_member_surname ON members(surname);

-- Full-Text index for article searches
CREATE FULLTEXT INDEX idx_blog_body_ft ON blog_entries(body_text);
SELECT * FROM blog_entries WHERE MATCH(body_text) AGAINST('optimization');

-- Spatial index for geospatial data
CREATE SPATIAL INDEX idx_region_geom ON regions(boundary);

-- Prefix index for lengthy URLs
CREATE INDEX idx_resource_url_prefix ON resources(url_path(20));

-- Invisible index for optimizer testing
CREATE INDEX idx_member_alt_email ON members(alt_email) INVISIBLE;

Index Architectures Deep Dive

1. B-Tree Structures

Optimal Scenarios: Equality constraints, range scans, and ordered outputs.

PostgreSQL:

CREATE INDEX idx_customer_email ON clients(email);
CREATE INDEX idx_sales_buyer_date ON sales(buyer_id, transaction_time DESC);

Oracle:

CREATE INDEX idx_staff_lastname ON staff(lastname);
CREATE INDEX idx_orders_cust_date ON orders(customer_id, order_date DESC);

MySQL:

CREATE INDEX idx_member_surname ON members(surname);
CREATE INDEX idx_purchases_buyer_date ON purchases(buyer_id, purchase_time DESC);

2. Hash Structures

Optimal Scenarios: Discrete point lookups without range or sort requirements.

PostgreSQL:

CREATE INDEX idx_session_token_hash ON sessions USING hash(auth_token);

MySQL:

CREATE INDEX idx_session_token_hash ON sessions USING hash(auth_token);

Note: Oracle relies on Hash Clusters rather than standalone hash indexes to acheive similar deterministic routing.

3. PostgreSQL-Specific Mechanisms

GIN (Generalized Inverted Index)

Optimal Scenarios: Composite elements like arrays, JSONB, and full-text vectors.

CREATE INDEX idx_product_tags ON catalog USING gin(category_tags);
SELECT * FROM catalog WHERE category_tags @> ARRAY['electronics', 'audio'];

GiST (Generalized Search Tree)

Optimal Scenarios: Geospatial bounding boxes, range overlaps, and nearest-neighbor searches.

CREATE INDEX idx_facilities_zone ON facilities USING gist(geo_boundary);
SELECT * FROM facilities WHERE ST_Contains(ST_MakeBox2D(ST_Point(-73.9, 40.7), ST_Point(-73.8, 40.8)), geo_boundary);

BRIN (Block Range Index)

Optimal Scenarios: Massive, physically ordered datasets like time-series logs where minimal index size is critical.

CREATE INDEX idx_metrics_timestamp ON metrics USING brin(recorded_at);
SELECT * FROM metrics WHERE recorded_at >= '2024-01-01' AND recorded_at < '2024-01-02';

Partial Indexes

Optimal Scenarios: Isolating a specific subset of rows, drastically reducing index bulk.

CREATE INDEX idx_active_accounts_email ON accounts(email) WHERE is_active = true;

Expression Indexes

Optimal Scenarios: Accelerating queries based on computed columns or functional transformations.

CREATE INDEX idx_accounts_lower_username ON accounts(lower(username));
SELECT * FROM accounts WHERE lower(username) = 'sysadmin';

Covering Indexes (INCLUDE)

Optimal Scenarios: Executing index-only scans by appending non-key payload to leaf nodes.

CREATE INDEX idx_accounts_email_cover ON accounts(email) INCLUDE (display_name, status);

4. Oracle-Specific Mechanisms

Bitmap Indexes

Optimal Scenarios: Read-mostly data warehouse columns with very few distinct values (low cardinality).

CREATE BITMAP INDEX idx_inventory_status ON inventory(availability);

Function-Based Indexes

Optimal Scenarios: Pre-calculating functon outcomes for query predicates, mirroring PG expression indexes.

CREATE INDEX idx_clients_upper_email ON clients(UPPER(email_addr));

Domain Indexes

Optimal Scenarios: Custom or specialized data types requiring bespoke indexing logic.

CREATE INDEX idx_manuals_text ON manuals(body_content) INDEXTYPE IS CTXSYS.CONTEXT;

5. MySQL-Specific Mechanisms

Full-Text Indexes

Optimal Scenarios: Natural language keyword extraction within massive text columns.

CREATE FULLTEXT INDEX idx_blogs_content ON blog_posts(content);
SELECT * FROM blog_posts WHERE MATCH(content) AGAINST('performance tuning');

Spatial Indexes

Optimal Scenarios: Geographic proximity searches and polygon intersection calculations.

CREATE SPATIAL INDEX idx_regions_boundary ON regions(boundary_geom);
SELECT * FROM regions WHERE ST_DWithin(boundary_geom, ST_GeomFromText('POINT(-73.9 40.7)'), 5000);

Prefix Indexes

Optimal Scenarios: Truncating lengthy strings to conserve storage while preserving selectivity.

CREATE INDEX idx_urls_path_prefix ON web_links(url_path(25));

Index Selection Guidelines

  • Prioritize High-Frequency Predicates: Build indexes on columns dominating WHERE, JOIN, and ORDER BY clauses.
  • Composite Index Ordering: Place equality columns first, followed by range columns, to maximize prefix utilization.
  • Avoid Over-Indexing: Every index adds write overhead and storage consumption; construct them only when query benefits outweigh maintenance costs.
  • Match Index to Data Nature:
    • Text Search: PG (GIN/GiST), MySQL (Full-Text), Oracle (Domain/CONTEXT).
    • Geospatial: PG (GiST/PostGIS), MySQL (Spatial), Oracle (Spatial).
    • Multi-valued/JSON: PG (GIN), others require relational normalization or virtual columns.
  • Schedule Routine Maintenance:
    • PostgreSQL: REINDEX
    • Oracle: ALTER INDEX ... REBUILD
    • MySQL: OPTIMIZE TABLE

Tags: PostgreSQL Oracle MySQL B-Tree Gin

Posted on Tue, 04 Aug 2026 16:39:04 +0000 by cartoonjunkie