如何通过多个连接查询来加速PostgreSQL组?

时间:2017-10-21 16:42:32

标签: postgresql

此查询搜索通常使用 product_grouping ID 99999购买的product_groupings 。由于此查询会扇动到包含product_grouping 99999的所有订单,然后重新连接以计算每个product_grouping具有的次数已经订购,并进入前10名。

有没有办法加快查询速度?

SELECT  product_groupings.*, count(product_groupings.id) AS product_groupings_count 
FROM "product_groupings" 
INNER JOIN "products" ON "product_groupings"."id" = "products"."product_grouping_id" 
INNER JOIN "variants" ON "products"."id" = "variants"."product_id" 
INNER JOIN "order_items" ON "variants"."id" = "order_items"."variant_id" 
INNER JOIN "shipments" ON "order_items"."shipment_id" = "shipments"."id" 
INNER JOIN "orders" ON "shipments"."order_id" = "orders"."id" 
INNER JOIN "shipments" "shipments_often_purchased_with_join" ON "orders"."id" = "shipments_often_purchased_with_join"."order_id" 
INNER JOIN "order_items" "order_items_often_purchased_with_join" ON "shipments_often_purchased_with_join"."id" = "order_items_often_purchased_with_join"."shipment_id" 
INNER JOIN "variants" "variants_often_purchased_with_join" ON "order_items_often_purchased_with_join"."variant_id" = "variants_often_purchased_with_join"."id" 
INNER JOIN "products" "products_often_purchased_with_join" ON "variants_often_purchased_with_join"."product_id" = "products_often_purchased_with_join"."id" 
WHERE "products_often_purchased_with_join"."product_grouping_id" = 99999 AND (product_groupings.id != 99999) AND "product_groupings"."state" = 'active' AND ("shipments"."state" NOT IN ('pending', 'cancelled')) 
GROUP BY product_groupings.id  
ORDER BY product_groupings_count desc LIMIT 10

模式:

CREATE TABLE product_groupings (
    id integer NOT NULL,
    state character varying(255) DEFAULT 'active'::character varying,
    brand_id integer,
    product_content_id integer,
    hierarchy_category_id integer,
    hierarchy_subtype_id integer,
    hierarchy_type_id integer,
    product_type_id integer,
    description text,
    keywords text,
    created_at timestamp without time zone,
    updated_at timestamp without time zone
);

CREATE INDEX index_product_groupings_on_brand_id ON product_groupings USING btree (brand_id);
CREATE INDEX index_product_groupings_on_hierarchy_category_id ON product_groupings USING btree (hierarchy_category_id);
CREATE INDEX index_product_groupings_on_hierarchy_subtype_id ON product_groupings USING btree (hierarchy_subtype_id);
CREATE INDEX index_product_groupings_on_hierarchy_type_id ON product_groupings USING btree (hierarchy_type_id);
CREATE INDEX index_product_groupings_on_name ON product_groupings USING btree (name);
CREATE INDEX index_product_groupings_on_product_content_id ON product_groupings USING btree (product_content_id);
CREATE INDEX index_product_groupings_on_product_type_id ON product_groupings USING btree (product_type_id);

ALTER TABLE ONLY product_groupings
    ADD CONSTRAINT product_groupings_pkey PRIMARY KEY (id);

CREATE TABLE products (
    id integer NOT NULL,
    name character varying(255) NOT NULL,
    prototype_id integer,
    deleted_at timestamp without time zone,
    created_at timestamp without time zone,
    updated_at timestamp without time zone,
    item_volume character varying(255),
    upc character varying(255),
    state character varying(255),
    volume_unit character varying(255),
    volume_value numeric,
    container_type character varying(255),
    container_count integer,
    upc_ext character varying(8),
    product_grouping_id integer,
    short_pack_size character varying(255),
    short_volume character varying(255),
    additional_upcs character varying(255)[] DEFAULT '{}'::character varying[]
);

CREATE INDEX index_products_on_additional_upcs ON products USING gin (additional_upcs);
CREATE INDEX index_products_on_deleted_at ON products USING btree (deleted_at);
CREATE INDEX index_products_on_name ON products USING btree (name);
CREATE INDEX index_products_on_product_grouping_id ON products USING btree (product_grouping_id);
CREATE INDEX index_products_on_prototype_id ON products USING btree (prototype_id);
CREATE INDEX index_products_on_upc ON products USING btree (upc);

ALTER TABLE ONLY products
    ADD CONSTRAINT products_pkey PRIMARY KEY (id);

CREATE TABLE variants (
    id integer NOT NULL,
    product_id integer NOT NULL,
    sku character varying(255) NOT NULL,
    name character varying(255),
    price numeric(8,2) DEFAULT 0.0 NOT NULL,
    deleted_at timestamp without time zone,
    supplier_id integer,
    created_at timestamp without time zone,
    updated_at timestamp without time zone,
    inventory_id integer,
    product_active boolean DEFAULT false NOT NULL,
    original_name character varying(255),
    original_item_volume character varying(255),
    protected boolean DEFAULT false NOT NULL,
    sale_price numeric(8,2) DEFAULT 0.0 NOT NULL
);

CREATE INDEX index_variants_on_inventory_id ON variants USING btree (inventory_id);
CREATE INDEX index_variants_on_product_id_and_deleted_at ON variants USING btree (product_id, deleted_at);
CREATE INDEX index_variants_on_sku ON variants USING btree (sku);
CREATE INDEX index_variants_on_state_attributes ON variants USING btree (deleted_at, product_active, protected, id);
CREATE INDEX index_variants_on_supplier_id ON variants USING btree (supplier_id);

ALTER TABLE ONLY variants
    ADD CONSTRAINT variants_pkey PRIMARY KEY (id);

CREATE TABLE order_items (
    id integer NOT NULL,
    price numeric(8,2),
    total numeric(8,2),
    variant_id integer NOT NULL,
    shipment_id integer,
    created_at timestamp without time zone,
    updated_at timestamp without time zone,
    quantity integer DEFAULT 1
);

CREATE INDEX index_order_items_on_shipment_id ON order_items USING btree (shipment_id);
CREATE INDEX index_order_items_on_variant_id ON order_items USING btree (variant_id);

ALTER TABLE ONLY order_items
    ADD CONSTRAINT order_items_pkey PRIMARY KEY (id);

CREATE TABLE shipments (
    id integer NOT NULL,
    order_id integer,
    shipping_method_id integer NOT NULL,
    number character varying,
    state character varying(255) DEFAULT 'pending'::character varying NOT NULL,
    created_at timestamp without time zone,
    updated_at timestamp without time zone,
    supplier_id integer,
    confirmed_at timestamp without time zone,
    canceled_at timestamp without time zone,
    out_of_hours boolean DEFAULT false NOT NULL,
    delivered_at timestamp without time zone,
    uuid uuid DEFAULT uuid_generate_v4()
);

CREATE INDEX index_shipments_on_order_id_and_supplier_id ON shipments USING btree (order_id, supplier_id);
CREATE INDEX index_shipments_on_state ON shipments USING btree (state);
CREATE INDEX index_shipments_on_supplier_id ON shipments USING btree (supplier_id);

ALTER TABLE ONLY shipments
    ADD CONSTRAINT shipments_pkey PRIMARY KEY (id);

CREATE TABLE orders (
    id integer NOT NULL,
    number character varying(255),
    ip_address character varying(255),
    state character varying(255),
    ship_address_id integer,
    active boolean DEFAULT true NOT NULL,
    completed_at timestamp without time zone,
    created_at timestamp without time zone,
    updated_at timestamp without time zone,
    tip_amount numeric(8,2) DEFAULT 0.0,
    confirmed_at timestamp without time zone,
    delivery_notes text,
    cancelled_at timestamp without time zone,
    courier boolean DEFAULT false NOT NULL,
    scheduled_for timestamp without time zone,
    client character varying(255),
    subscription_id character varying(255),
    pickup_detail_id integer,
);

CREATE INDEX index_orders_on_bill_address_id ON orders USING btree (bill_address_id);
CREATE INDEX index_orders_on_completed_at ON orders USING btree (completed_at);
CREATE UNIQUE INDEX index_orders_on_number ON orders USING btree (number);
CREATE INDEX index_orders_on_ship_address_id ON orders USING btree (ship_address_id);
CREATE INDEX index_orders_on_state ON orders USING btree (state);

ALTER TABLE ONLY orders
    ADD CONSTRAINT orders_pkey PRIMARY KEY (id);

查询计划:

Limit  (cost=685117.80..685117.81 rows=10 width=595) (actual time=33659.659..33659.661 rows=10 loops=1)
  Output: product_groupings.id, product_groupings.featured, product_groupings.searchable, product_groupings.state, product_groupings.brand_id, product_groupings.product_content_id, product_groupings.hierarchy_category_id, product_groupings.hierarchy_subtype_id, product_groupings.hierarchy_type_id, product_groupings.product_type_id, product_groupings.meta_description, product_groupings.meta_keywords, product_groupings.name, product_groupings.permalink, product_groupings.description, product_groupings.keywords, product_groupings.created_at, product_groupings.updated_at, product_groupings.tax_category_id, product_groupings.trimmed_name, (count(product_groupings.id))
  Buffers: shared hit=259132 read=85657, temp read=30892 written=30886
  I/O Timings: read=5542.213
  ->  Sort  (cost=685117.80..685117.81 rows=14 width=595) (actual time=33659.658..33659.659 rows=10 loops=1)
        Output: product_groupings.id, product_groupings.featured, product_groupings.searchable, product_groupings.state, product_groupings.brand_id, product_groupings.product_content_id, product_groupings.hierarchy_category_id, product_groupings.hierarchy_subtype_id, product_groupings.hierarchy_type_id, product_groupings.product_type_id, product_groupings.meta_description, product_groupings.meta_keywords, product_groupings.name, product_groupings.permalink, product_groupings.description, product_groupings.keywords, product_groupings.created_at, product_groupings.updated_at, product_groupings.tax_category_id, product_groupings.trimmed_name, (count(product_groupings.id))
        Sort Key: (count(product_groupings.id))
        Sort Method: top-N heapsort  Memory: 30kB
        Buffers: shared hit=259132 read=85657, temp read=30892 written=30886
        I/O Timings: read=5542.213
        ->  HashAggregate  (cost=685117.71..685117.75 rows=14 width=595) (actual time=33659.407..33659.491 rows=122 loops=1)
              Output: product_groupings.id, product_groupings.featured, product_groupings.searchable, product_groupings.state, product_groupings.brand_id, product_groupings.product_content_id, product_groupings.hierarchy_category_id, product_groupings.hierarchy_subtype_id, product_groupings.hierarchy_type_id, product_groupings.product_type_id, product_groupings.meta_description, product_groupings.meta_keywords, product_groupings.name, product_groupings.permalink, product_groupings.description, product_groupings.keywords, product_groupings.created_at, product_groupings.updated_at, product_groupings.tax_category_id, product_groupings.trimmed_name, count(product_groupings.id)
              Buffers: shared hit=259129 read=85657, temp read=30892 written=30886
              I/O Timings: read=5542.213
              ->  Hash Join  (cost=453037.24..685117.69 rows=14 width=595) (actual time=26019.889..33658.886 rows=181 loops=1)
                    Output: product_groupings.id, product_groupings.featured, product_groupings.searchable, product_groupings.state, product_groupings.brand_id, product_groupings.product_content_id, product_groupings.hierarchy_category_id, product_groupings.hierarchy_subtype_id, product_groupings.hierarchy_type_id, product_groupings.product_type_id, product_groupings.meta_description, product_groupings.meta_keywords, product_groupings.name, product_groupings.permalink, product_groupings.description, product_groupings.keywords, product_groupings.created_at, product_groupings.updated_at, product_groupings.tax_category_id, product_groupings.trimmed_name
                    Hash Cond: (order_items_often_purchased_with_join.variant_id = variants_often_purchased_with_join.id)
                    Buffers: shared hit=259129 read=85657, temp read=30892 written=30886
                    I/O Timings: read=5542.213
                    ->  Hash Join  (cost=452970.37..681530.70 rows=4693428 width=599) (actual time=22306.463..32908.056 rows=8417034 loops=1)
                          Output: product_groupings.id, product_groupings.featured, product_groupings.searchable, product_groupings.state, product_groupings.brand_id, product_groupings.product_content_id, product_groupings.hierarchy_category_id, product_groupings.hierarchy_subtype_id, product_groupings.hierarchy_type_id, product_groupings.product_type_id, product_groupings.meta_description, product_groupings.meta_keywords, product_groupings.name, product_groupings.permalink, product_groupings.description, product_groupings.keywords, product_groupings.created_at, product_groupings.updated_at, product_groupings.tax_category_id, product_groupings.trimmed_name, order_items_often_purchased_with_join.variant_id
                          Hash Cond: (products.product_grouping_id = product_groupings.id)
                          Buffers: shared hit=259080 read=85650, temp read=30892 written=30886
                          I/O Timings: read=5540.529
                          ->  Hash Join  (cost=381952.28..493289.49 rows=5047613 width=8) (actual time=21028.128..25416.504 rows=8417518 loops=1)
                                Output: products.product_grouping_id, order_items_often_purchased_with_join.variant_id
                                Hash Cond: (order_items_often_purchased_with_join.shipment_id = shipments_often_purchased_with_join.id)
                                Buffers: shared hit=249520 read=77729
                                I/O Timings: read=5134.878
                                ->  Seq Scan on public.order_items order_items_often_purchased_with_join  (cost=0.00..82689.54 rows=4910847 width=8) (actual time=0.003..1061.456 rows=4909856 loops=1)
                                      Output: order_items_often_purchased_with_join.shipment_id, order_items_often_purchased_with_join.variant_id
                                      Buffers: shared hit=67957
                                ->  Hash  (cost=373991.27..373991.27 rows=2274574 width=8) (actual time=21027.220..21027.220 rows=2117538 loops=1)
                                      Output: products.product_grouping_id, shipments_often_purchased_with_join.id
                                      Buckets: 262144  Batches: 1  Memory Usage: 82717kB
                                      Buffers: shared hit=181563 read=77729
                                      I/O Timings: read=5134.878
                                      ->  Hash Join  (cost=249781.35..373991.27 rows=2274574 width=8) (actual time=10496.552..20383.404 rows=2117538 loops=1)
                                            Output: products.product_grouping_id, shipments_often_purchased_with_join.id
                                            Hash Cond: (shipments.order_id = orders.id)
                                            Buffers: shared hit=181563 read=77729
                                            I/O Timings: read=5134.878
                                            ->  Hash Join  (cost=118183.04..233677.13 rows=1802577 width=8) (actual time=6080.516..14318.439 rows=1899610 loops=1)
                                                  Output: products.product_grouping_id, shipments.order_id
                                                  Hash Cond: (variants.product_id = products.id)
                                                  Buffers: shared hit=107220 read=55876
                                                  I/O Timings: read=5033.540
                                                  ->  Hash Join  (cost=83249.21..190181.06 rows=1802577 width=8) (actual time=4526.391..11330.434 rows=1899808 loops=1)
                                                        Output: variants.product_id, shipments.order_id
                                                        Hash Cond: (order_items.variant_id = variants.id)
                                                        Buffers: shared hit=88026 read=44439
                                                        I/O Timings: read=4009.465
                                                        ->  Hash Join  (cost=40902.30..138821.27 rows=1802577 width=8) (actual time=3665.477..8553.803 rows=1899816 loops=1)
                                                              Output: order_items.variant_id, shipments.order_id
                                                              Hash Cond: (order_items.shipment_id = shipments.id)
                                                              Buffers: shared hit=56654 read=43022
                                                              I/O Timings: read=3872.065
                                                              ->  Seq Scan on public.order_items  (cost=0.00..82689.54 rows=4910847 width=8) (actual time=0.003..2338.108 rows=4909856 loops=1)
                                                                    Output: order_items.variant_id, order_items.shipment_id
                                                                    Buffers: shared hit=55987 read=11970
                                                                    I/O Timings: read=1059.971
                                                              ->  Hash  (cost=38059.31..38059.31 rows=812284 width=8) (actual time=3664.973..3664.973 rows=834713 loops=1)
                                                                    Output: shipments.id, shipments.order_id
                                                                    Buckets: 131072  Batches: 1  Memory Usage: 32606kB
                                                                    Buffers: shared hit=667 read=31052
                                                                    I/O Timings: read=2812.094
                                                                    ->  Seq Scan on public.shipments  (cost=0.00..38059.31 rows=812284 width=8) (actual time=0.017..3393.420 rows=834713 loops=1)
                                                                          Output: shipments.id, shipments.order_id
                                                                          Filter: ((shipments.state)::text <> ALL ('{pending,cancelled}'::text[]))
                                                                          Rows Removed by Filter: 1013053
                                                                          Buffers: shared hit=667 read=31052
                                                                          I/O Timings: read=2812.094
                                                        ->  Hash  (cost=37200.34..37200.34 rows=1470448 width=8) (actual time=859.887..859.887 rows=1555657 loops=1)
                                                              Output: variants.product_id, variants.id
                                                              Buckets: 262144  Batches: 1  Memory Usage: 60768kB
                                                              Buffers: shared hit=31372 read=1417
                                                              I/O Timings: read=137.400
                                                              ->  Seq Scan on public.variants  (cost=0.00..37200.34 rows=1470448 width=8) (actual time=0.009..479.528 rows=1555657 loops=1)
                                                                    Output: variants.product_id, variants.id
                                                                    Buffers: shared hit=31372 read=1417
                                                                    I/O Timings: read=137.400
                                                  ->  Hash  (cost=32616.92..32616.92 rows=661973 width=8) (actual time=1553.664..1553.664 rows=688697 loops=1)
                                                        Output: products.product_grouping_id, products.id
                                                        Buckets: 131072  Batches: 1  Memory Usage: 26903kB
                                                        Buffers: shared hit=19194 read=11437
                                                        I/O Timings: read=1024.075
                                                        ->  Seq Scan on public.products  (cost=0.00..32616.92 rows=661973 width=8) (actual time=0.011..1375.757 rows=688697 loops=1)
                                                              Output: products.product_grouping_id, products.id
                                                              Buffers: shared hit=19194 read=11437
                                                              I/O Timings: read=1024.075
                                            ->  Hash  (cost=125258.00..125258.00 rows=1811516 width=12) (actual time=4415.081..4415.081 rows=1847746 loops=1)
                                                  Output: orders.id, shipments_often_purchased_with_join.order_id, shipments_often_purchased_with_join.id
                                                  Buckets: 262144  Batches: 1  Memory Usage: 79396kB
                                                  Buffers: shared hit=74343 read=21853
                                                  I/O Timings: read=101.338
                                                  ->  Hash Join  (cost=78141.12..125258.00 rows=1811516 width=12) (actual time=1043.228..3875.433 rows=1847746 loops=1)
                                                        Output: orders.id, shipments_often_purchased_with_join.order_id, shipments_often_purchased_with_join.id
                                                        Hash Cond: (shipments_often_purchased_with_join.order_id = orders.id)
                                                        Buffers: shared hit=74343 read=21853
                                                        I/O Timings: read=101.338
                                                        ->  Seq Scan on public.shipments shipments_often_purchased_with_join  (cost=0.00..37153.55 rows=1811516 width=8) (actual time=0.006..413.785 rows=1847766 loops=1)
                                                              Output: shipments_often_purchased_with_join.order_id, shipments_often_purchased_with_join.id
                                                              Buffers: shared hit=31719
                                                        ->  Hash  (cost=70783.52..70783.52 rows=2102172 width=4) (actual time=1042.239..1042.239 rows=2097229 loops=1)
                                                              Output: orders.id
                                                              Buckets: 262144  Batches: 1  Memory Usage: 73731kB
                                                              Buffers: shared hit=42624 read=21853
                                                              I/O Timings: read=101.338
                                                              ->  Seq Scan on public.orders  (cost=0.00..70783.52 rows=2102172 width=4) (actual time=0.012..553.606 rows=2097229 loops=1)
                                                                    Output: orders.id
                                                                    Buffers: shared hit=42624 read=21853
                                                                    I/O Timings: read=101.338
                          ->  Hash  (cost=20222.66..20222.66 rows=637552 width=595) (actual time=1278.121..1278.121 rows=626176 loops=1)
                                Output: product_groupings.id, product_groupings.featured, product_groupings.searchable, product_groupings.state, product_groupings.brand_id, product_groupings.product_content_id, product_groupings.hierarchy_category_id, product_groupings.hierarchy_subtype_id, product_groupings.hierarchy_type_id, product_groupings.product_type_id, product_groupings.meta_description, product_groupings.meta_keywords, product_groupings.name, product_groupings.permalink, product_groupings.description, product_groupings.keywords, product_groupings.created_at, product_groupings.updated_at, product_groupings.tax_category_id, product_groupings.trimmed_name
                                Buckets: 16384  Batches: 4  Memory Usage: 29780kB
                                Buffers: shared hit=9559 read=7921, temp written=10448
                                I/O Timings: read=405.651
                                ->  Seq Scan on public.product_groupings  (cost=0.00..20222.66 rows=637552 width=595) (actual time=0.020..873.844 rows=626176 loops=1)
                                      Output: product_groupings.id, product_groupings.featured, product_groupings.searchable, product_groupings.state, product_groupings.brand_id, product_groupings.product_content_id, product_groupings.hierarchy_category_id, product_groupings.hierarchy_subtype_id, product_groupings.hierarchy_type_id, product_groupings.product_type_id, product_groupings.meta_description, product_groupings.meta_keywords, product_groupings.name, product_groupings.permalink, product_groupings.description, product_groupings.keywords, product_groupings.created_at, product_groupings.updated_at, product_groupings.tax_category_id, product_groupings.trimmed_name
                                      Filter: ((product_groupings.id <> 99999) AND ((product_groupings.state)::text = 'active'::text))
                                      Rows Removed by Filter: 48650
                                      Buffers: shared hit=9559 read=7921
                                      I/O Timings: read=405.651
                    ->  Hash  (cost=66.86..66.86 rows=4 width=4) (actual time=2.223..2.223 rows=30 loops=1)
                          Output: variants_often_purchased_with_join.id
                          Buckets: 1024  Batches: 1  Memory Usage: 2kB
                          Buffers: shared hit=49 read=7
                          I/O Timings: read=1.684
                          ->  Nested Loop  (cost=0.17..66.86 rows=4 width=4) (actual time=0.715..2.211 rows=30 loops=1)
                                Output: variants_often_purchased_with_join.id
                                Buffers: shared hit=49 read=7
                                I/O Timings: read=1.684
                                ->  Index Scan using index_products_on_product_grouping_id on public.products products_often_purchased_with_join  (cost=0.08..5.58 rows=2 width=4) (actual time=0.074..0.659 rows=6 loops=1)
                                      Output: products_often_purchased_with_join.id
                                      Index Cond: (products_often_purchased_with_join.product_grouping_id = 99999)
                                      Buffers: shared hit=5 read=4
                                      I/O Timings: read=0.552
                                ->  Index Scan using index_variants_on_product_id_and_deleted_at on public.variants variants_often_purchased_with_join  (cost=0.09..30.60 rows=15 width=8) (actual time=0.222..0.256 rows=5 loops=6)
                                      Output: variants_often_purchased_with_join.id, variants_often_purchased_with_join.product_id
                                      Index Cond: (variants_often_purchased_with_join.product_id = products_often_purchased_with_join.id)
                                      Buffers: shared hit=44 read=3
                                      I/O Timings: read=1.132
Total runtime: 33705.142 ms

1 个答案:

答案 0 :(得分:0)

使用子选择获得了大约20倍的吞吐量增加;

SELECT  product_groupings.*, count(product_groupings.id) AS product_groupings_count 
FROM "product_groupings" 
INNER JOIN "products" ON "products"."product_grouping_id" = "product_groupings"."id" 
INNER JOIN "variants" ON "variants"."product_id" = "products"."id" 
INNER JOIN "order_items" ON "order_items"."variant_id" = "variants"."id" 
INNER JOIN "shipments" ON "shipments"."id" = "order_items"."shipment_id" 
WHERE ("product_groupings"."id" != 99999) 
  AND "product_groupings"."state" = 'active' 
  AND ("shipments"."state" NOT IN ('pending', 'cancelled')) 
  AND ("shipments"."order_id" IN (
    SELECT "shipments"."order_id" 
    FROM "shipments" 
    INNER JOIN "order_items" ON "order_items"."shipment_id" = "shipments"."id" 
    INNER JOIN "variants" ON "variants"."id" = "order_items"."variant_id" 
    INNER JOIN "products" ON "products"."id" = "variants"."product_id" 
    WHERE "products"."product_grouping_id" = 99999 AND ("shipments"."state" NOT IN ('pending', 'cancelled')) 
    GROUP BY "shipments"."order_id" 
    ORDER BY "shipments"."order_id" ASC
  )) 
GROUP BY product_groupings.id  
ORDER BY product_groupings_count desc 
LIMIT 10

虽然我欢迎任何进一步的优化。 :)