跪拜 Guibai
← Back to the summary

PostgreSQL Is Eating the Hard 20% That MySQL Can't Chew

Foreword

Recently, I've been mentoring a few friends who just entered the industry and noticed a common phenomenon: many people, after learning MySQL in university, never touch another database again.

When they encounter complex business scenarios at work, they try to force MySQL to handle them, ending up exhausted and frustrated.

Honestly, PostgreSQL's adoption in enterprise applications has been accelerating rapidly in recent years.

The 2026 DB-Engines ranking shows PostgreSQL firmly holding the fourth spot among global databases, behind only Oracle, MySQL, and SQL Server.

Today, I'll use this article to discuss PostgreSQL with everyone, hoping it will be helpful to you.

More project practices on my tech website: susan.net.cn/project

1. A Harsh Truth First

Some colleagues at work might say: "I've been using MySQL for years and it feels fine. Why should I learn PostgreSQL?"

Let me give a simple example: suppose your e-commerce system needs to query "the top 3 products by sales for each quarter of 2024." Writing this SQL in MySQL would take at least 20 lines, with various nested subqueries and window functions nested like Russian dolls.

PostgreSQL natively supports the RANK() window function, and you can accomplish it easily in 10 lines.

This isn't about being fancy; it's about productivity.

Your database choice determines whether countless future overtime nights are truly spent "solving problems with code" or "fighting with the database."

Moreover, many major domestic and international companies are already migrating from MySQL to PostgreSQL.

Apple, Instagram, Uber, Reddit, and even some of Alibaba's business lines are gradually embracing PostgreSQL.

There's a reason behind this — simply put, MySQL suits 80% of common scenarios, while PostgreSQL suits the remaining 20% of extremely complex "tough nuts" that demand high data consistency.

2. What Exactly Is PostgreSQL?

PostgreSQL is a powerful open-source relational database management system that originated from the POSTGRES project at the University of California, Berkeley in 1986.

After nearly 40 years of development, it has grown into one of the most advanced open-source databases in the world.

Its core features include:

The most fundamental difference between PostgreSQL and MySQL can be clarified with a diagram:

640 (93).png

Simply put, one excels at "doing it all," capable of handling everything; the other excels at "one killer move," achieving the extreme in specific scenarios.

3. Environment Setup

Let's get the environment running first.

PostgreSQL can run on various operating systems like Linux, Windows, and macOS.

For production environments, at least 8GB of memory is recommended.

3.1 Windows Users

For Windows beginners, the official graphical installer is recommended.

Steps:

3.2 macOS Users

For macOS, Homebrew installation is recommended:

# Install PostgreSQL
brew install postgresql

# Start the service
brew services start postgresql

# Connect to the database
psql postgres

If you're not comfortable with the command line, you can also download the .dmg installer from the official website and drag it to Applications.

3.3 Linux Users (Ubuntu/Debian)

For Ubuntu users, installing from the official repository is recommended to ensure the latest version:

# Add official repository
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
# Update and install
sudo apt update
sudo apt install postgresql postgresql-contrib
# Start the service
sudo systemctl start postgresql
# Switch to the postgres user
sudo -i -u postgres
psql

3.4 Installation Verification

Regardless of the system, you can quickly verify after installation with this command:

SELECT version();

Seeing output like "PostgreSQL 16.x" — congratulations, the installation was successful!

4. Mastering Basic SQL

4.1 Basic Database Operations

-- Create a database
CREATE DATABASE mydb;

-- View all databases
\l

-- Switch to a specific database
\c mydb

-- Create a user and grant privileges
CREATE USER myuser WITH PASSWORD 'mypass';
GRANT ALL PRIVILEGES ON DATABASE mydb TO myuser;

-- Delete a database (use with caution in production)
DROP DATABASE mydb;

4.2 Detailed Data Types

PostgreSQL supports a very rich set of data types, far exceeding MySQL:

Type Category Specific Types Description
Numeric INTEGER, BIGINT, NUMERIC(precision, scale), DOUBLE PRECISION NUMERIC for exact decimals, suitable for money
String VARCHAR(n), TEXT, CHAR(n) TEXT has no length limit
Date/Time DATE, TIME, TIMESTAMP, TIMESTAMPTZ, INTERVAL TIMESTAMPTZ includes timezone
Boolean BOOLEAN true/false
JSON JSON, JSONB JSONB is binary format, supports indexing
Array INTEGER[], TEXT[] Native array support
Network INET, CIDR, MACADDR IP address handling

4.3 Creating Tables and Constraints

-- Create a products table
CREATE TABLE products (
    id SERIAL PRIMARY KEY,                    -- Auto-increment primary key
    name VARCHAR(200) NOT NULL,               -- Product name, not null
    price NUMERIC(10,2) CHECK (price > 0),    -- Price must be greater than 0
    stock INTEGER DEFAULT 0,                  -- Stock, default 0
    category VARCHAR(50),
    tags TEXT[],                              -- Array type for storing tags
    created_at TIMESTAMPTZ DEFAULT NOW()      -- Timestamp with timezone
);

4.4 Basic CRUD Operations

-- Insert data
INSERT INTO products (name, price, stock, category, tags)
VALUES ('Mechanical Keyboard', 399.00, 50, 'Peripherals', ARRAY['keyboard', 'esports']),
       ('Wireless Mouse', 129.00, 100, 'Peripherals', ARRAY['mouse', 'wireless']);

-- Query data
SELECT * FROM products WHERE price BETWEEN 100 AND 500;

-- Update data
UPDATE products SET stock = stock - 1 WHERE id = 1;

-- Delete data
DELETE FROM products WHERE stock = 0;

5. MVCC Multi-Version Concurrency Control

Some colleagues at work might ask: What is the essential difference in concurrency performance between PostgreSQL and MySQL? Why is PostgreSQL said to be more suitable for high-concurrency write scenarios?

This is thanks to MVCC (Multi-Version Concurrency Control).

PostgreSQL's concurrency promise is very simple: Reads never block writes, and writes never block reads.

5.1 Core Principles of MVCC

In PostgreSQL, each transaction receives a unique transaction ID, called XID.

The core difference of MVCC is: MySQL implements it through locking mechanisms, where reading data might be blocked by write operations; PostgreSQL implements it through snapshot isolation, meaning there is never a read-write mutual lock.

XMIN and XMAX are two hidden fields present in every data row. XMIN is written during insertion (the transaction ID that created this version), and XMAX is written during update/deletion (the transaction ID that deleted this version). By comparing XIDs, transaction visibility can be determined.

5.2 Transaction Isolation Levels

PostgreSQL supports four isolation levels, defaulting to READ COMMITTED, and also provides the strict SERIALIZABLE level:

What happens if two transactions modify the same row simultaneously in PostgreSQL?

image.png

Under the SERIALIZABLE isolation level, this throws an error, and the application layer can choose to retry or abort.

6. PostgreSQL's Index Arsenal

Some colleagues at work might encounter this situation: they clearly built an index on a field, but slow queries still pile up. This is because PostgreSQL's indexing system is much more complex and powerful than MySQL's — different index types suit completely different scenarios.

6.1 The Full Index Type Family

MySQL mainly relies on B-tree indexes, while PostgreSQL offers a rich arsenal of index types:

Index Type Typical Scenario Key Point
B-tree Equality & range queries, sorting Default type, supports = < > <= >= ORDER BY
Hash Equality queries only Does not support range or sorting
GIN Arrays, JSONB, full-text search Inverted index structure, suitable for "multi-value" queries
GiST Geospatial, full-text search Supports various distance queries
BRIN Large tables, sequentially inserted data Small size, suitable for "coarse-grained" range filtering
Bloom High-selectivity multi-column queries Bitmap filtering, reduces the number of composite indexes

B-tree is the default index type, but it also has the most pitfalls. For example, a query like LIKE '%keyword%' will not use a B-tree index; in this case, GIN or full-text search should be considered.

GIN Index in Action: Accelerating JSONB Queries

Pairing PostgreSQL's JSONB type with a GIN index can improve query performance by hundreds of times:

-- Create a table with a JSONB field
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    details JSONB
);

-- Insert test data
INSERT INTO products (details) VALUES
('{"name": "Laptop", "specs": {"cpu": "i7", "ram": "16GB"}, "tags": ["electronics", "sale"]}'),
('{"name": "Keyboard", "specs": {"switch": "mechanical", "connection": "wired"}, "tags": ["peripherals"]}');

-- Query without index (slow)
SELECT * FROM products WHERE details -> 'specs' ->> 'cpu' = 'i7';

-- Create GIN index
CREATE INDEX idx_products_details ON products USING GIN (details);

-- Fast query after indexing (300x faster)
SELECT details -> 'name' AS name, details -> 'specs' AS specs
FROM products
WHERE details @> '{"specs": {"cpu": "i7"}}';

The @> here is the JSONB containment operator, which leverages the GIN index for fast lookups.

6.2 Expression Indexes and Partial Indexes

PostgreSQL supports two advanced index types that require complex configuration in MySQL:

-- Expression index: Query by lowercase email, avoiding function calls that invalidate the index
CREATE INDEX idx_users_lower_email ON users (lower(email));
-- Now this can use the index directly
SELECT * FROM users WHERE lower(email) = '[email protected]';

-- Partial index: Only index active users, greatly reducing index size
CREATE INDEX idx_users_active ON users (email) WHERE active = true;

The value of these two features lies in precise indexing — not all data needs acceleration, only index the part you query most often, reducing both storage and write costs.

6.3 Execution Plan Analysis

Use EXPLAIN ANALYZE to view the actual execution plan of an SQL statement:

EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM products WHERE price < 100;

In the output, focus on whether it shows Seq Scan (full table scan, slow) or Index Scan (index scan, fast).

If you prefer a graphical interface, you can use pgAdmin for visual diagnostics.

7. JSONB and Full-Text Search

7.1 Making a Relational Database "Unstructured"

JSONB parses JSON data into a binary structure for storage, supports indexing, and its query performance far exceeds MySQL's JSON type.

Core JSONB Operators:

-- Create table
CREATE TABLE user_profiles (
    id SERIAL PRIMARY KEY,
    profile JSONB
);

INSERT INTO user_profiles (profile) VALUES
('{"name": "Zhang San", "age": 30, "address": {"city": "Beijing", "zip": "100000"}, "skills": ["Java", "Python"]}'),
('{"name": "Li Si", "age": 25, "address": {"city": "Shanghai", "zip": "200000"}, "skills": ["Go", "Rust"]}');

-- Query: -> returns JSON object, ->> returns text
SELECT
    profile -> 'name' AS name_json,
    profile ->> 'name' AS name_text,
    profile -> 'address' ->> 'city' AS city
FROM user_profiles;

-- Conditional query: containment operator @>
SELECT * FROM user_profiles 
WHERE profile @> '{"skills": ["Java"]}';

-- JSON path query: Use #>> to locate directly
SELECT profile #>> '{address,city}' FROM user_profiles;

7.2 Full-Text Search: Turning the Database into a Search Engine

PostgreSQL has a built-in full-text search engine, using two core types: tsvector (text search vector) and tsquery (text search query):

-- Create test table
CREATE TABLE articles (
    id SERIAL PRIMARY KEY,
    title TEXT,
    content TEXT
);

INSERT INTO articles (title, content) VALUES
('Getting Started with PostgreSQL Full-Text Search', 'Full-text search allows PostgreSQL to search text like a search engine'),
('JSONB Practical Guide', 'The JSONB type supports efficient semi-structured data storage and querying');

-- Basic full-text search
SELECT title FROM articles
WHERE to_tsvector('english', title || ' ' || content) @@ to_tsquery('english', 'search');

-- Best practice: Create a GIN index to accelerate full-text search
ALTER TABLE articles ADD COLUMN search_vector tsvector
GENERATED ALWAYS AS (to_tsvector('english', title || ' ' || content)) STORED;

CREATE INDEX idx_articles_search ON articles USING GIN(search_vector);

-- Sort by relevance
SELECT title, ts_rank(search_vector, query) AS rank
FROM articles, to_tsquery('english', 'search') query
WHERE search_vector @@ query
ORDER BY rank DESC;

GENERATED ALWAYS AS ... STORED is a clever design in PostgreSQL — the generated column is automatically maintained, used directly in queries without needing to compute it on the fly each time, balancing both performance and convenience.

8. Java Integration in Practice

As Java backend developers, we need to integrate database operations into real projects.

Here, we'll use Spring Boot + MyBatis Plus as an example.

8.1 Add Dependencies

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- PostgreSQL Driver -->
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.7.0</version>
</dependency>
<!-- MyBatis Plus -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.7</version>
</dependency>

8.2 Configure Data Source

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/mydb
    username: postgres
    password: your_password
    driver-class-name: org.postgresql.Driver

# Connection pool configuration (HikariCP recommended)
datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 30000

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: auto

8.3 Entity Class and Mapper

@Data
@TableName("products")
public class Product {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private BigDecimal price;
    private Integer stock;
    private String category;
    private List<String> tags;  // PostgreSQL natively supports arrays
}

@Mapper
public interface ProductMapper extends BaseMapper<Product> {
    // Custom query: products within a price range
    @Select("SELECT * FROM products WHERE price BETWEEN #{min} AND #{max}")
    List<Product> selectByPriceRange(@Param("min") BigDecimal min, 
                                      @Param("max") BigDecimal max);
}

8.4 Service Layer Usage

@Service
public class ProductService {
    @Autowired
    private ProductMapper productMapper;
    
    // Paginated query
    public IPage<Product> pageList(int pageNum, int pageSize) {
        Page<Product> page = new Page<>(pageNum, pageSize);
        return productMapper.selectPage(page, null);
    }
    
    // Lambda conditional query
    public List<Product> getInStockProducts() {
        LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>();
        wrapper.gt(Product::getStock, 0)
                .orderByDesc(Product::getPrice);
        return productMapper.selectList(wrapper);
    }
    
    // JSONB field query (using native SQL)
    @Select("SELECT * FROM products WHERE details @> #{jsonCondition}::jsonb")
    List<Product> selectByJsonCondition(@Param("jsonCondition") String jsonCondition);
}

9. PostgreSQL vs MySQL Comparison

9.1 Core Differences at a Glance

Comparison Dimension PostgreSQL MySQL
Core Positioning Enterprise-grade full-featured database, emphasizing standard compliance and complex computation Internet-grade OLTP database, emphasizing high performance and ease of use
SQL Standard Compliance Over 90% compatibility with SQL:2023 standard About 70% compatibility, many advanced features heavily stripped down
Architecture Model Single storage engine + pluggable extensions Multiple storage engines, primarily InnoDB
MVCC Implementation Snapshot isolation, reads and writes never block Relies on undo log, lock contention exists
Serializable Isolation Strict SSI implementation, high performance Essentially full table locking, extremely poor performance
Extensibility Extremely strong plugin ecosystem, supporting time-series/vector/geospatial, etc. Limited extensibility, ecosystem focuses on sharding
JSON Support JSONB binary storage, GIN index, 300x faster JSON type, weaker query performance
Advanced SQL Native support for window functions, CTEs, full-text search Partially supported since MySQL 8.0
Open Source License PostgreSQL License (permissive BSD-like) GPLv2, modifications to source code require open-sourcing
Applicable Scenarios Complex queries, data analysis, finance, geographic information High-concurrency web applications, content management, rapid prototyping

PostgreSQL's greatest advantage lies in its functional completeness and standards compliance.

PostgreSQL has the most complete support for SQL standards, which is crucial for complex applications and data analysis.

More and more major domestic companies are starting to migrate from MySQL to PostgreSQL, valuing precisely its SQL standard compatibility and plugin extension capabilities.

10. Pros, Cons, and Applicable Scenarios

10.1 Advantages

10.2 Disadvantages

10.3 Applicable Scenarios

Scenarios strongly recommended for PostgreSQL:

Scenarios more suitable for MySQL:

More project practices on my tech website: susan.net.cn/project

Summary

MySQL is a "cleaver" type of database — sharp and simple, one move conquers all.

PostgreSQL is a "Swiss Army knife" type of database. It is fully functional but has a higher learning threshold.

Neither is definitively better, but you need to understand the respective prowess of these two sharp tools.

I suggest every Java backend engineer spend two months:

Once you become familiar with these advanced features of PostgreSQL, you will discover — complex requirements can truly be solved with a single line of SQL in the database, rather than cobbling together a thousand lines of Java if-else in your business code.

This is a fundamental shift in database thinking.

I hope this article not only helps you get started with PostgreSQL but also opens a window for you into multi-modal data management and complex business modeling.