高级数据类型应用实战

person 落叶    watch_later 2026-04-28 21:39:13
visibility 16    class PostgreSQL    bookmark 专栏

一、JSONB 高级应用

1. JSONB 索引策略

-- 创建测试表
CREATE TABLE product_catalog (
    id SERIAL PRIMARY KEY,
    product_data JSONB NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 插入百万级测试数据
INSERT INTO product_catalog (product_data)
SELECT 
    jsonb_build_object(
        'id', g,
        'name', 'Product ' || g,
        'sku', 'SKU-' || LPAD(g::TEXT, 8, '0'),
        'price', (random() * 1000)::NUMERIC(10,2),
        'category', (ARRAY['Electronics', 'Clothing', 'Books', 'Home', 'Sports'])[floor(random() * 5) + 1],
        'brand', (ARRAY['Apple', 'Samsung', 'Nike', 'Sony', 'LG'])[floor(random() * 5) + 1],
        'stock', floor(random() * 1000)::INT,
        'tags', (ARRAY['new', 'sale', 'bestseller', 'clearance'])[floor(random() * 4) + 1],
        'specs', jsonb_build_object(
            'weight', (random() * 10)::NUMERIC(10,2),
            'dimensions', jsonb_build_object('width', random() * 100, 'height', random() * 100),
            'color', (ARRAY['Red', 'Blue', 'Black', 'White'])[floor(random() * 4) + 1]
        ),
        'reviews', (
            SELECT jsonb_agg(
                jsonb_build_object(
                    'user', 'user' || floor(random() * 1000),
                    'rating', floor(random() * 5) + 1,
                    'comment', 'Review comment ' || floor(random() * 100)
                )
            )
            FROM generate_series(1, floor(random() * 20)::INT)
        )
    )
FROM generate_series(1, 1000000) g;

-- 不同索引策略对比

-- 1. 默认 GIN 索引(支持所有 JSONB 操作符)
CREATE INDEX idx_product_gin ON product_catalog USING gin(product_data);

-- 2. 指定操作的 GIN 索引(更小更快)
CREATE INDEX idx_product_gin_ops ON product_catalog USING gin(product_data jsonb_path_ops);

-- 3. 针对特定字段的索引
CREATE INDEX idx_product_category ON product_catalog ((product_data->>'category'));
CREATE INDEX idx_product_price ON product_catalog ((product_data->>'price'));

-- 4. 表达式索引(用于类型转换)
CREATE INDEX idx_product_price_numeric ON product_catalog (((product_data->>'price')::NUMERIC));

-- 5. 针对嵌套对象的索引
CREATE INDEX idx_product_specs_color ON product_catalog ((product_data->'specs'->>'color'));

-- 6. 部分索引(只索引活跃产品)
CREATE INDEX idx_active_products ON product_catalog 
    USING gin(product_data) 
    WHERE product_data->>'stock' > '0';

-- 索引性能测试
EXPLAIN (ANALYZE, BUFFERS) 
SELECT * FROM product_catalog 
WHERE product_data @> '{"category": "Electronics"}';

EXPLAIN (ANALYZE, BUFFERS) 
SELECT * FROM product_catalog 
WHERE product_data->>'price' > '500' 
  AND product_data->>'category' = 'Electronics';

2. JSONB 高级查询模式

-- 路径查询和 JSONPath
SELECT 
    product_data,
    jsonb_path_query(product_data, '$.specs.dimensions') AS dimensions,
    jsonb_path_exists(product_data, '$.reviews[*].rating ? (@ >= 4)') AS has_good_reviews
FROM product_catalog;

-- 条件聚合
SELECT 
    product_data->>'category' AS category,
    AVG((product_data->>'price')::NUMERIC) AS avg_price,
    SUM((product_data->>'stock')::INT) AS total_stock,
    COUNT(*) FILTER (WHERE product_data ? 'reviews') AS has_reviews,
    jsonb_agg(DISTINCT product_data->>'brand') AS brands
FROM product_catalog
WHERE product_data->>'price' IS NOT NULL
GROUP BY product_data->>'category'
ORDER BY avg_price DESC;

-- 复杂嵌套查询
WITH review_stats AS (
    SELECT 
        id,
        product_data->>'name' AS product_name,
        AVG((review->>'rating')::INT) AS avg_rating,
        COUNT(*) AS review_count,
        jsonb_agg(review->>'comment') AS all_comments
    FROM product_catalog,
    LATERAL jsonb_array_elements(product_data->'reviews') AS review
    GROUP BY id, product_name
)
SELECT 
    product_name,
    avg_rating,
    review_count,
    all_comments
FROM review_stats
WHERE avg_rating > 4.5 AND review_count > 10
ORDER BY avg_rating DESC
LIMIT 20;

-- 动态键值查询
SELECT 
    product_data,
    jsonb_each(product_data->'specs') AS spec_items,
    jsonb_each_text(product_data) AS all_fields
FROM product_catalog
WHERE product_data->>'id' = '100';

-- 递归 JSONB 展开
WITH RECURSIVE json_keys AS (
    SELECT 
        id,
        key,
        value,
        key AS path
    FROM product_catalog,
    LATERAL jsonb_each(product_data)
    WHERE jsonb_typeof(product_data) = 'object'
  
    UNION ALL
  
    SELECT 
        jk.id,
        elem.key,
        elem.value,
        jk.path || '.' || elem.key
    FROM json_keys jk,
    LATERAL jsonb_each(jk.value) elem
    WHERE jsonb_typeof(jk.value) = 'object'
)
SELECT DISTINCT path FROM json_keys ORDER BY path;

3. JSONB 更新与合并策略

-- 批量更新 JSONB 字段
-- 为所有电子产品添加保修信息
UPDATE product_catalog 
SET product_data = jsonb_set(
    product_data, 
    '{warranty}', 
    '"2 years"'
)
WHERE product_data->>'category' = 'Electronics';

-- 数组追加元素
UPDATE product_catalog 
SET product_data = jsonb_set(
    product_data,
    '{tags}',
    product_data->'tags' || '"new_arrival"'
)
WHERE product_data->>'category' = 'Books';

-- 条件更新(使用 CASE)
UPDATE product_catalog 
SET product_data = CASE 
    WHEN (product_data->>'price')::NUMERIC < 100 THEN
        product_data || jsonb_build_object('discount', 10, 'is_on_sale', true)
    WHEN (product_data->>'price')::NUMERIC < 500 THEN
        product_data || jsonb_build_object('discount', 5, 'is_on_sale', true)
    ELSE
        product_data - 'discount' - 'is_on_sale'
END
WHERE product_data->>'stock' > '0';

-- 使用 jsonb_patch 进行深度合并
CREATE OR REPLACE FUNCTION jsonb_deep_merge(a JSONB, b JSONB)
RETURNS JSONB AS $$
SELECT 
    jsonb_object_agg(
        COALESCE(ka, kb),
        CASE 
            WHEN ka IS NULL THEN a->kb
            WHEN kb IS NULL THEN a->ka
            WHEN jsonb_typeof(a->ka) = 'object' AND jsonb_typeof(b->kb) = 'object' 
                THEN jsonb_deep_merge(a->ka, b->kb)
            ELSE b->kb
        END
    )
FROM (SELECT jsonb_object_keys(a) AS ka) ka
FULL JOIN (SELECT jsonb_object_keys(b) AS kb) kb ON ka = kb;
$$ LANGUAGE SQL STRICT IMMUTABLE;

-- 使用深度合并更新
UPDATE product_catalog 
SET product_data = jsonb_deep_merge(
    product_data,
    '{"specs": {"grade": "A+", "warranty": {"months": 24, "type": "premium"}}}'
)
WHERE product_data->>'brand' = 'Apple';

二、数组类型高级应用

1. 数组作为标签系统

-- 创建标签系统
CREATE TABLE articles (
    id SERIAL PRIMARY KEY,
    title TEXT,
    content TEXT,
    tags TEXT[],
    categories TEXT[],
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 创建 GIN 索引加速数组查询
CREATE INDEX idx_articles_tags ON articles USING gin(tags);
CREATE INDEX idx_articles_categories ON articles USING gin(categories);

-- 插入带标签的数据
INSERT INTO articles (title, content, tags, categories) VALUES
    ('PostgreSQL Arrays Tutorial', 'Content about arrays...', 
     ARRAY['postgresql', 'arrays', 'tutorial'], ARRAY['database', 'sql']),
    ('Advanced JSONB in PG', 'Learn JSONB...', 
     ARRAY['postgresql', 'jsonb', 'advanced'], ARRAY['database', 'nosql']),
    ('Performance Tuning Tips', 'How to optimize...', 
     ARRAY['performance', 'tuning', 'postgresql'], ARRAY['dba', 'optimization']);

-- 高效标签查询
-- 查找包含任意指定标签的文章
SELECT * FROM articles 
WHERE tags && ARRAY['postgresql', 'performance'];

-- 查找包含所有指定标签的文章
SELECT * FROM articles 
WHERE tags @> ARRAY['postgresql', 'tutorial'];

-- 标签统计(最受欢迎标签)
SELECT 
    tag,
    COUNT(*) AS article_count,
    ARRAY_AGG(DISTINCT title) AS articles
FROM articles,
LATERAL unnest(tags) AS tag
GROUP BY tag
ORDER BY article_count DESC;

-- 协同过滤:查找相似文章
WITH article_tags AS (
    SELECT tags FROM articles WHERE id = 1
),
similar_articles AS (
    SELECT 
        a.id,
        a.title,
        a.tags,
        array_length(ARRAY(
            SELECT unnest(a.tags) 
            INTERSECT 
            SELECT unnest(at.tags)
        ), 1) AS common_tags_count
    FROM articles a, article_tags at
    WHERE a.id != 1
)
SELECT * FROM similar_articles 
WHERE common_tags_count > 0 
ORDER BY common_tags_count DESC, array_length(tags, 1) ASC
LIMIT 10;

2. 数组作为时间序列聚合器

-- 使用数组存储时序数据
CREATE TABLE sensor_data (
    sensor_id INTEGER,
    date DATE,
    hourly_readings NUMERIC[24],           -- 24小时数据数组
    daily_stats NUMERIC[8],                 -- 最大、最小、平均等
    anomalies BOOLEAN[],
    PRIMARY KEY (sensor_id, date)
);

-- 插入小时级数据
INSERT INTO sensor_data (sensor_id, date, hourly_readings)
VALUES (
    1, 
    CURRENT_DATE,
    ARRAY(
        SELECT (random() * 100)::NUMERIC 
        FROM generate_series(1, 24)
    )
);

-- 更新特定小时的数据
UPDATE sensor_data 
SET hourly_readings[12] = 85.5
WHERE sensor_id = 1 AND date = CURRENT_DATE;

-- 计算每日统计
UPDATE sensor_data 
SET daily_stats = ARRAY[
    (SELECT MIN(v) FROM unnest(hourly_readings) v),
    (SELECT MAX(v) FROM unnest(hourly_readings) v),
    (SELECT AVG(v) FROM unnest(hourly_readings) v),
    (SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY v) 
     FROM unnest(hourly_readings) v),
    (SELECT STDDEV(v) FROM unnest(hourly_readings) v)
];

-- 检测异常值(超出3倍标准差)
UPDATE sensor_data 
SET anomalies = ARRAY(
    SELECT 
        v > (daily_stats[2] + 3 * daily_stats[5]) 
        OR v < (daily_stats[1] - 3 * daily_stats[5])
    FROM unnest(hourly_readings) v
);

-- 滑动窗口统计
SELECT 
    sensor_id,
    date,
    hourly_readings[1:8] AS morning_readings,
    hourly_readings[9:17] AS day_readings,
    hourly_readings[18:24] AS night_readings
FROM sensor_data
WHERE date >= CURRENT_DATE - INTERVAL '7 days';

-- 生成报告
SELECT 
    sensor_id,
    date,
    unnest(hourly_readings) AS hour_value,
    generate_series(1, 24) AS hour
FROM sensor_data
CROSS JOIN LATERAL unnest(hourly_readings) WITH ORDINALITY AS t(value, idx)
WHERE date = CURRENT_DATE;

三、范围类型应用

1. 会议室预订系统

-- 启用范围索引扩展
CREATE EXTENSION IF NOT EXISTS btree_gist;

-- 会议室预定表
CREATE TABLE meeting_room_reservations (
    id SERIAL PRIMARY KEY,
    room_id INTEGER NOT NULL,
    booker TEXT NOT NULL,
    meeting_time TSTZRANGE NOT NULL,
    participants TEXT[],
    metadata JSONB,
    -- 防止同一房间时间重叠
    EXCLUDE USING gist (room_id WITH =, meeting_time WITH &&)
);

-- 插入预定数据
INSERT INTO meeting_room_reservations (room_id, booker, meeting_time, participants)
VALUES 
    (101, 'Alice', '[2024-06-15 09:00, 2024-06-15 11:00)', ARRAY['Bob', 'Charlie']),
    (101, 'David', '[2024-06-15 11:30, 2024-06-15 13:00)', ARRAY['Eve']);

-- 冲突检查
WITH check_reservation AS (
    SELECT room_id, tstzrange('2024-06-15 10:00', '2024-06-15 12:00') AS new_range
)
SELECT 
    r.room_id,
    r.meeting_time,
    r.booker,
    r.meeting_time && c.new_range AS overlaps
FROM meeting_room_reservations r, check_reservation c
WHERE r.room_id = c.room_id 
  AND r.meeting_time && c.new_range;

-- 查找可用时间段
WITH RECURSIVE available_slots AS (
    SELECT 
        room_id,
        LOWER(meeting_time) AS start_time,
        UPPER(meeting_time) AS end_time
    FROM meeting_room_reservations
    WHERE room_id = 101 AND meeting_time && tstzrange('2024-06-15 08:00', '2024-06-15 20:00')
    ORDER BY start_time
  
    UNION ALL
  
    SELECT 
        room_id,
        end_time,
        LEAD(start_time) OVER (ORDER BY start_time)
    FROM meeting_room_reservations
    WHERE room_id = 101
)
SELECT 
    room_id,
    start_time AS available_start,
    LEAD(start_time) OVER (ORDER BY start_time) AS available_end
FROM available_slots;

2. 价格区间策略

-- 价格区间策略表
CREATE TABLE price_strategies (
    id SERIAL PRIMARY KEY,
    product_type TEXT,
    price_range NUMRANGE,
    discount_rate NUMERIC(4,2),
    strategy_name TEXT,
    valid_period DATERANGE,
    EXCLUDE USING gist (product_type WITH =, price_range WITH &&)
);

-- 插入价格策略
INSERT INTO price_strategies (product_type, price_range, discount_rate, strategy_name) VALUES
    ('Electronics', '[0, 500)', 0, 'Standard'),
    ('Electronics', '[500, 1000)', 5, 'Silver'),
    ('Electronics', '[1000, 2000)', 10, 'Gold'),
    ('Electronics', '[2000, inf)', 15, 'Platinum');

-- 根据价格自动应用策略
CREATE OR REPLACE FUNCTION get_discount_rate(p_product_type TEXT, p_price NUMERIC)
RETURNS NUMERIC AS $$
DECLARE
    v_rate NUMERIC;
BEGIN
    SELECT discount_rate INTO v_rate
    FROM price_strategies
    WHERE product_type = p_product_type 
      AND p_price <@ price_range;
  
    RETURN COALESCE(v_rate, 0);
END;
$$ LANGUAGE plpgsql STABLE;

-- 查找价格区间重叠
SELECT 
    a.strategy_name AS strategy_1,
    b.strategy_name AS strategy_2,
    a.price_range * b.price_range AS overlap_range
FROM price_strategies a, price_strategies b
WHERE a.id < b.id 
  AND a.product_type = b.product_type
  AND a.price_range && b.price_range;

四、复合类型实际应用

-- 创建地址复合类型
CREATE TYPE address_type AS (
    street TEXT,
    city TEXT,
    state VARCHAR(2),
    zip_code VARCHAR(10),
    country VARCHAR(50),
    geo_location POINT
);

-- 创建联系人复合类型
CREATE TYPE contact_type AS (
    phone VARCHAR(20),
    email VARCHAR(255),
    alt_phone VARCHAR(20),
    preferred_contact_method VARCHAR(20)
);

-- 使用复合类型的客户表
CREATE TABLE customers_advanced (
    id SERIAL PRIMARY KEY,
    name VARCHAR(200),
    billing_address address_type,
    shipping_address address_type,
    contact contact_type,
    preferences JSONB,
    metadata JSONB
);

-- 高效的复合类型插入
INSERT INTO customers_advanced (name, billing_address, shipping_address, contact)
VALUES (
    'Acme Corporation',
    ROW('123 Business St', 'New York', 'NY', '10001', 'USA', point(40.7128, -74.0060))::address_type,
    ROW('456 Warehouse Ave', 'Newark', 'NJ', '07101', 'USA', point(40.7357, -74.1724))::address_type,
    ROW('+1-212-555-0123', 'contact@acme.com', '+1-212-555-0124', 'email')::contact_type
);

-- 查询复合类型字段
SELECT 
    name,
    (billing_address).city,
    (billing_address).zip_code,
    (contact).email,
    (contact).preferred_contact_method
FROM customers_advanced
WHERE (billing_address).state = 'NY'
  AND (contact).email IS NOT NULL;

-- 更新复合类型字段
UPDATE customers_advanced 
SET shipping_address.state = 'NJ'  -- 注意括号
WHERE name = 'Acme Corporation';

-- 使用复合类型函数
CREATE OR REPLACE FUNCTION format_address(addr address_type)
RETURNS TEXT AS $$
BEGIN
    RETURN format('%s, %s, %s %s, %s', 
                  addr.street, addr.city, addr.state, addr.zip_code, addr.country);
END;
$$ LANGUAGE plpgsql IMMUTABLE;

SELECT 
    name,
    format_address(billing_address) AS formatted_billing,
    format_address(shipping_address) AS formatted_shipping
FROM customers_advanced;

五、全文搜索实战

1. 博客系统全文搜索

-- 创建博客表
CREATE TABLE blog_posts (
    id SERIAL PRIMARY KEY,
    title TEXT NOT NULL,
    content TEXT NOT NULL,
    author TEXT,
    tags TEXT[],
    published_at TIMESTAMP,
    search_vector TSVECTOR,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 创建全文搜索索引
CREATE INDEX idx_blog_search ON blog_posts USING gin(search_vector);
CREATE INDEX idx_blog_published ON blog_posts (published_at DESC);

-- 更新搜索向量的触发器
CREATE OR REPLACE FUNCTION blog_post_search_vector_update()
RETURNS TRIGGER AS $$
BEGIN
    NEW.search_vector :=
        setweight(to_tsvector('english', COALESCE(NEW.title, '')), 'A') ||
        setweight(to_tsvector('english', COALESCE(NEW.content, '')), 'B') ||
        setweight(to_tsvector('english', COALESCE(NEW.author, '')), 'C') ||
        setweight(to_tsvector('english', array_to_string(NEW.tags, ' ')), 'D');
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_blog_search_update
    BEFORE INSERT OR UPDATE ON blog_posts
    FOR EACH ROW EXECUTE FUNCTION blog_post_search_vector_update();

-- 插入测试数据
INSERT INTO blog_posts (title, content, author, tags, published_at) VALUES
    ('Getting Started with PostgreSQL Full-Text Search', 
     'This article explains how to implement full-text search in PostgreSQL...',
     'John Doe', ARRAY['postgresql', 'search', 'tutorial'], NOW()),
    ('Advanced PostgreSQL Performance Tuning', 
     'Learn how to optimize your PostgreSQL database for better performance...',
     'Jane Smith', ARRAY['performance', 'tuning', 'postgresql'], NOW());

-- 基础全文搜索
SELECT 
    title,
    author,
    ts_rank(search_vector, query) AS relevance
FROM blog_posts, 
     to_tsquery('english', 'PostgreSQL & search') AS query
WHERE search_vector @@ query
ORDER BY relevance DESC;

-- 带高亮的搜索
SELECT 
    title,
    ts_headline(content, query, 'StartSel=***, StopSel=***, MaxWords=30, MinWords=15') AS highlighted_content
FROM blog_posts,
     phraseto_tsquery('english', 'database performance tuning') AS query
WHERE search_vector @@ query;

-- 搜索建议(模糊匹配)
SELECT 
    title,
    similarity(title, 'postgress')
FROM blog_posts
WHERE title % 'postgress'  -- 使用 pg_trgm 相似度
ORDER BY similarity(title, 'postgress') DESC;

-- 分类搜索统计
SELECT 
    tag,
    COUNT(*) AS post_count,
    AVG(ts_rank(search_vector, query)) AS avg_relevance
FROM blog_posts,
LATERAL unnest(tags) AS tag,
to_tsquery('english', 'performance') AS query
WHERE search_vector @@ query
GROUP BY tag
ORDER BY post_count DESC;

-- 多语言全文搜索
CREATE TEXT SEARCH CONFIGURATION multilingual (COPY = simple);
ALTER TEXT SEARCH CONFIGURATION multilingual 
    ALTER MAPPING FOR asciiword, asciihword, hword_asciipart
    WITH english_stem, french_stem, german_stem;

2. 实时搜索提示

-- 搜索日志表
CREATE TABLE search_log (
    id SERIAL PRIMARY KEY,
    search_term TEXT NOT NULL,
    search_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    result_count INTEGER,
    user_id INTEGER
);

-- 创建 trigram 索引用于模糊匹配
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX idx_search_term_trigram ON search_log USING gin(search_term gin_trgm_ops);

-- 热门搜索建议
SELECT 
    search_term,
    COUNT(*) AS search_count,
    AVG(result_count) AS avg_results
FROM search_log
WHERE search_time > NOW() - INTERVAL '7 days'
  AND search_term % 'databae'  -- 模糊匹配
GROUP BY search_term
ORDER BY search_count DESC, similarity(search_term, 'databae') DESC
LIMIT 10;

-- 搜索自动完成
CREATE OR REPLACE FUNCTION search_autocomplete(prefix TEXT, limit_count INT DEFAULT 10)
RETURNS TABLE(term TEXT, frequency INT, similarity_score REAL) AS $$
BEGIN
    RETURN QUERY
    SELECT 
        search_term,
        COUNT(*) AS frequency,
        MAX(similarity(search_term, prefix)) AS similarity_score
    FROM search_log
    WHERE search_term % prefix OR search_term ILIKE prefix || '%'
    GROUP BY search_term
    ORDER BY frequency DESC, similarity_score DESC
    LIMIT limit_count;
END;
$$ LANGUAGE plpgsql STABLE;

-- 使用搜索建议
SELECT * FROM search_autocomplete('postgre', 5);

六、数据类型性能优化

1. 存储优化

-- 数据类型存储大小对比
CREATE TABLE data_type_benchmark (
    id SERIAL PRIMARY KEY,
    int_value INTEGER,
    bigint_value BIGINT,
    numeric_value NUMERIC(20,4),
    text_value TEXT,
    char50 CHAR(50),
    varchar50 VARCHAR(50),
    json_value JSONB,
    json_pretty JSON,
    array_int INTEGER[],
    timestamp_value TIMESTAMP,
    uuid_value UUID,
    bytea_value BYTEA
);

-- 查看实际存储大小
SELECT 
    pg_size_pretty(pg_total_relation_size('data_type_benchmark')) AS total_size,
    pg_size_pretty(pg_indexes_size('data_type_benchmark')) AS index_size;

-- 表压缩(使用 pg_repack 或手动 VACUUM FULL)
VACUUM FULL data_type_benchmark;

-- TOAST 管理(大字段自动压缩)
ALTER TABLE large_text_table SET (toast_tuple_target = 8160);

2. 索引选择指南

-- 不同数据类型的推荐索引

-- 1. B-Tree(默认,适用于等值和范围查询)
CREATE INDEX idx_btree_name ON users(name);           -- TEXT
CREATE INDEX idx_btree_created ON orders(created_at); -- TIMESTAMP
CREATE INDEX idx_btree_price ON products(price);       -- NUMERIC

-- 2. GIN(JSONB、数组、全文搜索)
CREATE INDEX idx_gin_data ON json_table USING gin(json_data);
CREATE INDEX idx_gin_tags ON articles USING gin(tags);
CREATE INDEX idx_gin_search ON articles USING gin(search_vector);

-- 3. GiST(范围、几何、全文搜索)
CREATE INDEX idx_gist_range ON reservations USING gist(time_range);
CREATE INDEX idx_gist_location ON locations USING gist(geo_point);
CREATE INDEX idx_gist_tsvector ON articles USING gin(search_vector); -- GIN 更优

-- 4. BRIN(大表、自然排序的列)
CREATE INDEX idx_brin_created ON logs USING brin(created_at);
CREATE INDEX idx_brin_id ON huge_table USING brin(id);

-- 5. Hash(仅等值查询,不推荐)
CREATE INDEX idx_hash_phone ON users USING hash(phone);

-- 6. SP-GiST(点、线、多边形等空间数据)
CREATE INDEX idx_spgist_location ON locations USING spgist(geo_point);

3. 查询优化示例

-- 优化 JSONB 查询
-- 不好的做法
SELECT * FROM products 
WHERE product_data->>'price' > '100'  -- 字符串比较,不会使用索引
  AND product_data->>'in_stock' = 'true';

-- 好的做法:使用正确类型和索引
CREATE INDEX idx_product_price ON products (((product_data->>'price')::NUMERIC));
CREATE INDEX idx_product_in_stock ON products ((product_data->'in_stock'));

SELECT * FROM products 
WHERE (product_data->>'price')::NUMERIC > 100
  AND product_data->'in_stock' = 'true';

-- 优化数组查询
-- 不好的做法
SELECT * FROM articles WHERE 'postgresql' = ANY(tags);  -- 不使用索引

-- 好的做法
CREATE INDEX idx_articles_tags ON articles USING gin(tags);
SELECT * FROM articles WHERE tags @> ARRAY['postgresql'];

-- 优化全文搜索
-- 不好的做法
SELECT * FROM articles WHERE content LIKE '%database%';  -- 全表扫描

-- 好的做法
CREATE INDEX idx_articles_search ON articles USING gin(to_tsvector('english', content));
SELECT * FROM articles WHERE to_tsvector('english', content) @@ to_tsquery('english', 'database');
评论区
评论列表
menu