Tại sao truy vấn EXISTS của tôi thực hiện quét chỉ mục thay vì tìm kiếm chỉ mục?


15

Tôi đang làm việc để tối ưu hóa một số truy vấn.

Đối với truy vấn bên dưới,

SET STATISTICS IO ON;
DECLARE @OrderStartDate DATETIME2 = '27 feb 2016';
DECLARE @OrderEndDate  DATETIME2 = '28 feb 2016';

SELECT  o.strBxOrderNo
        , o.sintOrderStatusID
        , o.sintOrderChannelID
        , o.sintOrderTypeID
        , o.sdtmOrdCreated
        , o.sintMarketID
        , o.strOrderKey
        , o.strOfferCode
        , o.strCurrencyCode
        , o.decBCShipFullPrice
        , o.decBCShipFinal
        , o.decBCShipTax
        , o.decBCTotalAmount
        , o.decWrittenTotalAmount
        , o.decBCWrittenTotalAmount
        , o.decBCShipOfferDisc
        , o.decBCShipOverride
        , o.decTotalAmount
        , o.decShipTax
        , o.decShipFinal
        , o.decShipOverride
        , o.decShipOfferDisc
        , o.decShipFullPrice
        , o.lngAccountParticipantID
        , CONVERT(DATE, o.sdtmOrdCreated, 120) as OrderCreatedDateConverted
FROM    tablebackups.dbo.tblBOrder o
WHERE   o.sdtmOrdCreated >= @OrderStartDate
        AND o.sdtmOrdCreated < @OrderEndDate
        AND EXISTS  (
            SELECT  *
            FROM    tablebackups.dbo.tblBOrderItem oi 
            WHERE   oi.strBxOrderNo = o.strBxOrderNo
            AND     oi.decCatItemPrice > 0
        )
OPTION (RECOMPILE);

Tôi đã tạo chỉ mục LỌC sau:

-- table dbo.tblBorderItem
CREATE NONCLUSTERED INDEX IX_tblBOrderItem_decCatItemPrice_INCL 
ON dbo.tblBorderItem 
( 
     strBxOrderNo ASC
    , sintOrderSeqNo ASC
    , decCatItemPrice   
)   
INCLUDE 
(
    blnChargeShipping
    , decBCCatItemPrice
    , decBCCostPrice
    , decBCFinalPrice
    , decBCOfferDiscount
    , decBCOverrideDiscount
    , decBCTaxAmount
    , decCostPrice
    , decFinalPrice
    , decOfferDiscount
    , decOverrideDiscount
    , decTaxAmount
    , decWasPrice
    , dtmOrdItemCreated
    , sintOrderItemStatusId
    , sintOrderItemType
    , sintQuantity
    , strItemNo
)  
WHERE decCatItemPrice > 0 
WITH (DROP_EXISTING = ON, FILLFACTOR = 95);

Chỉ mục này không chỉ được sử dụng cho truy vấn này, có những truy vấn khác sử dụng cùng chỉ mục này, do đó các cột INCLUDED.

Đối với truy vấn này nói riêng, tôi chỉ muốn kiểm tra (EXISTS) nếu một đơn hàng có bất kỳ mục nào decCatItemPrice > 0.

SQL Server đang thực hiện quét chỉ mục như bạn có thể thấy trong các hình ảnh bên dưới.

  • Thống kê vừa được cập nhật.
  • Bảng mục có 41.208 hàng trong thử nghiệm.

Xin lưu ý, tôi không chọn bất kỳ cột nào từ bảng mục.

Bảng mặt hàng này có 164.309.397 trực tiếp. Tôi muốn tránh quét ở đó.

câu hỏi:

Tại sao SQL Server không thực hiện tìm kiếm chỉ mục?

Có những yếu tố / điều khác tôi nên xem xét để cải thiện truy vấn này không?

(4537 row(s) affected) Table 'tblBorder'. Scan count 1, logical reads
116, physical reads 0, read-ahead reads 0, lob logical reads 0, lob
physical reads 0, lob read-ahead reads 0. Table 'tblBorderItem'. Scan
count 1, logical reads 689, physical reads 0, read-ahead reads 0, lob
logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

nhập mô tả hình ảnh ở đây nhập mô tả hình ảnh ở đây

đây là định nghĩa và chỉ mục trên bảng tblBorderItem

    IF OBJECT_ID('[dbo].[tblBorderItem]') IS NOT NULL 
    DROP TABLE [dbo].[tblBorderItem] 
    GO
    CREATE TABLE [dbo].[tblBorderItem] ( 
    [strBxOrderNo]                VARCHAR(20)                      NOT NULL,
    [sintOrderSeqNo]              SMALLINT                         NOT NULL,
    [sintOrderItemStatusId]       SMALLINT                         NOT NULL,
    [sintNameStructureID]         SMALLINT                         NOT NULL,
    [strItemNo]                   VARCHAR(20)                      NOT NULL,
    [sintQuantity]                SMALLINT                         NOT NULL,
    [strCurrencyCode]             VARCHAR(3)                       NOT NULL,
    [decCostPrice]                DECIMAL(18,4)                    NOT NULL,
    [decCatItemPrice]             DECIMAL(18,2)                    NOT NULL,
    [decOfferDiscount]            DECIMAL(18,2)                    NOT NULL,
    [decOverrideDiscount]         DECIMAL(18,2)                    NOT NULL,
    [decFinalPrice]               DECIMAL(18,2)                    NOT NULL,
    [decTaxAmount]                DECIMAL(18,2)                    NOT NULL,
    [strBCCurrencyCode]           VARCHAR(3)                       NOT NULL,
    [decBCCostPrice]              DECIMAL(18,4)                    NOT NULL,
    [decBCCatItemPrice]           DECIMAL(18,4)                    NOT NULL,
    [decBCOfferDiscount]          DECIMAL(18,4)                    NOT NULL,
    [decBCOverrideDiscount]       DECIMAL(18,4)                    NOT NULL,
    [decBCFinalPrice]             DECIMAL(18,4)                    NOT NULL,
    [decBCTaxAmount]              DECIMAL(18,4)                    NOT NULL,
    [dtmOrdItemCreated]           DATETIME                         NOT NULL,
    [blnChargeShipping]           BIT                              NOT NULL,
    [lngTimeOfOrderQtyOnHand]     INT                                  NULL,
    [sdtmTimeOfOrderDueDate]      SMALLDATETIME                        NULL,
    [lngProdSetSeqNo]             INT                                  NULL,
    [lngProdRelationId]           INT                                  NULL,
    [lngProdRelationMemberId]     INT                                  NULL,
    [decWasPrice]                 DECIMAL(18,2)                        NULL,
    [sintOrderItemType]           SMALLINT                             NULL,
    [tsRowVersion]                TIMESTAMP                            NULL,
    [sdtmOrderItemStatusUpdated]  SMALLDATETIME                        NULL,
    CONSTRAINT   [PK_tblBOrderItem]  
PRIMARY KEY CLUSTERED    
([strBxOrderNo] asc, [sintOrderSeqNo] asc) 
WITH FILLFACTOR = 100)

    GO

    CREATE NONCLUSTERED INDEX 
    [IX_tblBOrderItem__dtmOrdItemCreated] 
       ON [dbo].[tblBorderItem] ([dtmOrdItemCreated] asc)
       WITH FILLFACTOR = 100


    CREATE NONCLUSTERED INDEX [IX_tblBOrderItem__sintOrderItemStatusId] 
       ON [dbo].[tblBorderItem] ([sintOrderItemStatusId] asc)
       INCLUDE ([sdtmOrderItemStatusUpdated], 
    [sintOrderSeqNo], [strBxOrderNo], [strItemNo])
       WITH FILLFACTOR = 100

CREATE NONCLUSTERED INDEX [IX_tblBOrderItem__
sintOrderItemStatusId_decFinalPrice_
sdtmOrderItemStatusUpdated_
include_strBxOrderNo] 
   ON [dbo].[tblBorderItem] 
([sintOrderItemStatusId] asc, 
 [decFinalPrice] asc, 
 [sdtmOrderItemStatusUpdated] asc)
   INCLUDE ([strBxOrderNo])
   WITH FILLFACTOR = 100

CREATE NONCLUSTERED INDEX [IX_tblBOrderItem__strBxOrderNo] 
   ON [dbo].[tblBorderItem] 
([strBxOrderNo] asc)
   WITH FILLFACTOR = 100


CREATE NONCLUSTERED INDEX [IX_tblBOrderItem__strItemNo] 
   ON [dbo].[tblBorderItem] ([strItemNo] asc)
   WITH FILLFACTOR = 100

CREATE NONCLUSTERED INDEX 
[IX_tblBOrderItem_decCatItemPrice_INCL] 
   ON [dbo].[tblBorderItem] 
([strBxOrderNo] asc, [sintOrderSeqNo] asc, [decCatItemPrice] asc)
   INCLUDE ([blnChargeShipping], 
[decBCCatItemPrice], [decBCCostPrice], [decBCFinalPrice], 
[decBCOfferDiscount], [decBCOverrideDiscount], 
[decBCTaxAmount], [decCostPrice], [decFinalPrice], 
[decOfferDiscount], [decOverrideDiscount], 
[decTaxAmount], [decWasPrice], [dtmOrdItemCreated], 
[sintOrderItemStatusId], [sintOrderItemType], 
[sintQuantity], [strItemNo])
   WHERE ([decCatItemPrice]>(0))
   WITH FILLFACTOR = 95

đây là định nghĩa và chỉ mục trên bảng tblBorder

IF OBJECT_ID('[dbo].[tblBorder]') IS NOT NULL 
DROP TABLE [dbo].[tblBorder] 
GO
CREATE TABLE [dbo].[tblBorder] ( 
[strBxOrderNo]                VARCHAR(20)                      NOT NULL,
[uidOrderUniqueID]            UNIQUEIDENTIFIER                 NOT NULL,
[sintOrderStatusID]           SMALLINT                         NOT NULL,
[sintOrderChannelID]          SMALLINT                         NOT NULL,
[sintOrderTypeID]             SMALLINT                         NOT NULL,
[blnIsBasket]                 BIT                              NOT NULL,
[sdtmOrdCreated]              SMALLDATETIME                    NOT NULL,
[sintMarketID]                SMALLINT                         NOT NULL,
[strOrderKey]                 VARCHAR(20)                      NOT NULL,
[strOfferCode]                VARCHAR(20)                      NOT NULL,
[lngShippedToParticipantID]   INT                              NOT NULL,
[lngOrderedByParticipantID]   INT                              NOT NULL,
[lngShipToAddressID]          INT                              NOT NULL,
[lngAccountAddressID]         INT                              NOT NULL,
[lngAccountParticipantID]     INT                              NOT NULL,
[lngOrderedByAddressID]       INT                              NOT NULL,
[lngOrderTakenBy]             INT                              NOT NULL,
[strCurrencyCode]             VARCHAR(3)                       NOT NULL,
[decShipFullPrice]            DECIMAL(18,2)                    NOT NULL,
[decShipOfferDisc]            DECIMAL(18,2)                    NOT NULL,
[decShipOverride]             DECIMAL(18,2)                    NOT NULL,
[decShipFinal]                DECIMAL(18,2)                    NOT NULL,
[decShipTax]                  DECIMAL(18,2)                    NOT NULL,
[strBCCurrencyCode]           VARCHAR(3)                       NOT NULL,
[decBCShipFullPrice]          DECIMAL(18,4)                    NOT NULL,
[decBCShipOfferDisc]          DECIMAL(18,4)                    NOT NULL,
[decBCShipOverride]           DECIMAL(18,4)                    NOT NULL,
[decBCShipFinal]              DECIMAL(18,4)                    NOT NULL,
[decBCShipTax]                DECIMAL(18,4)                    NOT NULL,
[decTotalAmount]              DECIMAL(18,2)                    NOT NULL,
[decBCTotalAmount]            DECIMAL(18,4)                    NOT NULL,
[decWrittenTotalAmount]       DECIMAL(18,2)                        NULL,
[decBCWrittenTotalAmount]     DECIMAL(18,4)                        NULL,
[blnProRataShipping]          BIT                              NOT NULL,
[blnChargeWithFirstShipment]  BIT                              NOT NULL,
[sintShippingServiceLevelID]  SMALLINT                         NOT NULL,
[sintShippingMethodID]        SMALLINT                         NOT NULL,
[sdtmDoNotShipUntil]          SMALLDATETIME                        NULL,
[blnHoldUntilComplete]        BIT                              NOT NULL,
[tsRowVersion]                TIMESTAMP                            NULL,
CONSTRAINT   [PK_tblBOrder]  
PRIMARY KEY CLUSTERED    
([strBxOrderNo] asc) WITH FILLFACTOR = 100)

GO

CREATE NONCLUSTERED INDEX 
[IX_tblBOrder__lngAccountAddressID] 
   ON [dbo].[tblBorder] 
   ([lngAccountAddressID] asc, [sintOrderStatusID] asc)
   WITH FILLFACTOR = 100

CREATE NONCLUSTERED INDEX 
[IX_tblBOrder__lngAccountParticipantID] 
   ON [dbo].[tblBorder] 
   ([lngAccountParticipantID] asc)
   WITH FILLFACTOR = 100

CREATE NONCLUSTERED INDEX 
[IX_tblBOrder__lngOrderedByAddressID] 
   ON [dbo].[tblBorder] 
   ([lngOrderedByAddressID] asc, [sintOrderStatusID] asc)
   WITH FILLFACTOR = 100

CREATE NONCLUSTERED INDEX 
[IX_tblBOrder__lngOrderedByParticipantID] 
   ON [dbo].[tblBorder] ([lngOrderedByParticipantID] asc)
   WITH FILLFACTOR = 100

CREATE NONCLUSTERED INDEX 
[IX_tblBOrder__lngShippedToParticipantID] 
   ON [dbo].[tblBorder] 
   ([lngShippedToParticipantID] asc)
   WITH FILLFACTOR = 100

CREATE NONCLUSTERED INDEX 
[IX_tblBOrder__lngShipToAddressID] 
   ON [dbo].[tblBorder] 
   ([lngShipToAddressID] asc, [sintOrderStatusID] asc)
   WITH FILLFACTOR = 100

CREATE NONCLUSTERED INDEX 
[IX_tblBOrder__sdtmOrdCreated_sintMarketID__include_strBxOrderNo] 
   ON [dbo].[tblBorder] 
   ([sdtmOrdCreated] asc, [sintMarketID] asc)
   INCLUDE ([strBxOrderNo])
   WITH FILLFACTOR = 100

CREATE NONCLUSTERED INDEX 
[IX_tblBOrder_sdtmOrdCreated_INCL] 
   ON [dbo].[tblBorder] 
   ([sdtmOrdCreated] asc)
   INCLUDE ([decBCShipFinal], [decBCShipFullPrice], 
            [decBCShipOfferDisc], [decBCShipOverride], 
            [decBCShipTax], [decBCTotalAmount], [decBCWrittenTotalAmount], 
            [decShipFinal], [decShipFullPrice], [decShipOfferDisc], 
            [decShipOverride], [decShipTax], [decTotalAmount], 
            [decWrittenTotalAmount], [lngAccountParticipantID], 
            [lngOrderedByParticipantID], [sintMarketID], 
            [sintOrderChannelID], [sintOrderStatusID], 
            [sintOrderTypeID], [strBxOrderNo], [strCurrencyCode], 
            [strOfferCode], [strOrderKey])
   WITH FILLFACTOR = 100

CREATE NONCLUSTERED 
INDEX [IX_tblBOrder_sintMarketID_sdtmOrdCreated] 
   ON [dbo].[tblBorder] 
   ([sintMarketID] asc, [sdtmOrdCreated] asc)
   INCLUDE ([sintOrderChannelID], [strBxOrderNo])
   WITH FILLFACTOR = 100

CREATE NONCLUSTERED 
INDEX [IX_tblBOrder__sintOrderChannelID_sdtmOrdCreated_INCL] 
   ON [dbo].[tblBorder] 
   ([sintOrderChannelID] asc, [sdtmOrdCreated] asc)
   INCLUDE ([decBCShipFinal], [decBCShipFullPrice], 
   [decBCShipTax], [decShipFinal], [decShipFullPrice], 
   [decShipTax], [lngAccountParticipantID], [sintMarketID], 
   [sintOrderTypeID], [strBxOrderNo], 
   [strCurrencyCode], [strOrderKey])
   WITH FILLFACTOR = 100

CREATE NONCLUSTERED INDEX [IX_tblBOrder_strBxOrderNo_sdtmOrdCreated_incl] 
   ON [dbo].[tblBorder] ([strBxOrderNo] asc, 
   [sdtmOrdCreated] asc)
   INCLUDE ([sintOrderChannelID], [sintOrderTypeID], [sintMarketID], 
   [strOrderKey], [lngAccountParticipantID], [strCurrencyCode], 
   [decShipFullPrice], [decShipFinal], [decShipTax], 
   [decBCShipFullPrice], [decBCShipFinal], 
   [decBCShipTax])

Phần kết luận

Tôi đã áp dụng chỉ mục của mình trên hệ thống LIVE và cập nhật quy trình được lưu trữ của mình để sử dụng SMALLDATETIME, để khớp với các loại dữ liệu trong cơ sở dữ liệu cho các cột liên quan.

Sau đó, khi nhìn vào kế hoạch truy vấn tôi thấy hình dưới đây:

nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây

đó chính xác là cách tôi muốn

Tôi nghĩ rằng trình tối ưu hóa truy vấn trong trường hợp này đã làm rất tốt để có được kế hoạch truy vấn tốt nhất trên cả hai môi trường và tôi rất vui vì tôi đã không thêm bất kỳ gợi ý truy vấn nào.

Tôi đã học được với 3 câu trả lời được đăng. cảm ơn Max Vernon , Paul WhiteDaniel Hutmacher vì câu trả lời của họ.

Câu trả lời:


15

Nếu bạn muốn có kết quả tốt từ trình tối ưu hóa truy vấn, bạn phải cẩn thận về các loại dữ liệu .

Các biến của bạn được nhập là datetime2 :

DECLARE @OrderStartDate datetime2 = '27 feb 2016';
DECLARE @OrderEndDate  datetime2 = '28 feb 2016';

Nhưng cột này được so sánh với được gõ smalldatetime (tùy theo từng sdtm tiền tố gợi ý!):

[sdtmOrdCreated] SMALLDATETIME NOT NULL

Tính không tương thích về loại khiến trình tối ưu hóa khó có thể thực hiện ước tính số lượng thẻ kết quả thông qua chuyển đổi loại , như được hiển thị trong kế hoạch thực hiện xml:

<ScalarOperator ScalarString="GetRangeWithMismatchedTypes([@OrderStartDate],NULL,(22))">
<ScalarOperator ScalarString="GetRangeWithMismatchedTypes(NULL,[@OrderEndDate],(10))">

Ước tính hiện tại có thể hoặc không thể chính xác (có thể không). Khắc phục sự không tương thích về loại có thể hoặc không thể giải quyết hoàn toàn vấn đề lựa chọn kế hoạch của bạn, nhưng đó là điều đầu tiên (dễ dàng!) Tôi sẽ khắc phục trước khi xem xét sâu hơn về vấn đề:

DECLARE @OrderStartDate smalldatetime = CONVERT(smalldatetime, '20160227', 112);
DECLARE @OrderEndDate smalldatetime = CONVERT(smalldatetime, '20160228', 112);

Luôn kiểm tra tính chính xác của ước tính cardinality và lý do cho bất kỳ sự khác biệt nào trước khi quyết định viết lại truy vấn hoặc sử dụng gợi ý.

Xem bài viết SQLblog.com của tôi, "Tìm kiếm động và chuyển đổi tiềm ẩn" để biết thêm chi tiết về tìm kiếm động.

Cập nhật: Sửa kiểu dữ liệu giúp bạn có kế hoạch tìm kiếm mà bạn muốn. Các lỗi ước tính cardinality gây ra bởi chuyển đổi loại trước đó đã cho bạn kế hoạch chậm hơn.


11

SQL Server đang thực hiện quét chỉ mục vì nó cho rằng rẻ hơn so với tìm kiếm cho mỗi hàng bắt buộc. Rất có thể, SQL Server là chính xác, dựa trên các lựa chọn mà nó có trong thiết lập của bạn.

Lưu ý rằng SQL Server thực sự có thể thực hiện quét phạm vi trên chỉ mục, trái ngược với quét toàn bộ chỉ mục.

Nếu bạn cung cấp DDL cho cả hai bảng, cùng với các chỉ mục khác mà bạn có thể có, chúng tôi có thể giúp bạn thực hiện việc này ít tốn tài nguyên hơn.

Như một lưu ý phụ, không bao giờ sử dụng ngày chữ như thế. Thay vì:

DECLARE @OrderStartDate DATETIME2 = '27 feb 2016';
DECLARE @OrderEndDate  DATETIME2 = '28 feb 2016';

dùng cái này:

DECLARE @OrderStartDate DATETIME2 = '2016-02-27T00:00:00.0000';
DECLARE @OrderEndDate  DATETIME2 = '2016-02-28T00:00:00.0000';

Bài viết của Aaron có thể giúp làm rõ điều đó.


6

Để thêm vào câu trả lời của Max, có lẽ tôi sẽ cố gắng chia truy vấn của bạn thành hai phần:

DECLARE @OrderStartDate DATETIME2 = {d '2016-02-27'};
DECLARE @OrderEndDate   DATETIME2 = {d '2016-02-28'};

--- Work variable declarations:
DECLARE @minOrderNo varchar(20), @maxOrderNo varchar(20);

--- Find the lowest and highest order number respectively for
--- your date range:
SELECT @minOrderNo=MIN(strBxOrderNo),
       @maxOrderNo=MAX(strBxOrderNo)
FROM    dbo.tblBOrder o
WHERE   o.sdtmOrdCreated >= @OrderStartDate AND
        o.sdtmOrdCreated <  @OrderEndDate;

--- Join orders and order items on their respective clustering keys.
SELECT    o.strBxOrderNo
        , o.sintOrderStatusID
        , o.sintOrderChannelID
        , o.sintOrderTypeID
        , o.sdtmOrdCreated
        , o.sintMarketID
        , o.strOrderKey
        , o.strOfferCode
        , o.strCurrencyCode
        , o.decBCShipFullPrice
        , o.decBCShipFinal
        , o.decBCShipTax
        , o.decBCTotalAmount
        , o.decWrittenTotalAmount
        , o.decBCWrittenTotalAmount
        , o.decBCShipOfferDisc
        , o.decBCShipOverride
        , o.decTotalAmount
        , o.decShipTax
        , o.decShipFinal
        , o.decShipOverride
        , o.decShipOfferDisc
        , o.decShipFullPrice
        , o.lngAccountParticipantID
        , CONVERT(DATE, o.sdtmOrdCreated, 120) as OrderCreatedDateConverted
FROM dbo.tblBOrder AS o
INNER /*MERGE*/ JOIN dbo.tblBOrderItem AS oi ON
    o.strBxOrderNo>=@minOrderNo AND      --- OrderNo filter on "orders"
    o.strBxOrderNo<=@maxOrderNo AND
    oi.strBxOrderNo=o.strBxOrderNo AND   --- Equijoin
    oi.strBxOrderNo>=@minOrderNo AND     --- OrderNo filter on "order items"
    oi.strBxOrderNo<=@maxOrderNo AND
    oi.decCatItemPrice > 0               --- Item price filter on "order items"
OPTION (RECOMPILE);

Truy vấn này sẽ (a) loại bỏ toán tử Sắp xếp (tốn kém vì nó chặn và yêu cầu cấp bộ nhớ), (b) tạo Hợp nhất (mà bạn có thể buộc với gợi ý tham gia, nhưng nó sẽ tự động xảy ra với đủ dữ liệu ). Như một phần thưởng, nó cũng sẽ (c) loại bỏ Quét chỉ mục.

Nói chung, truy vấn MIN / MAX sử dụng một chỉ mục tối ưu cao trên bảng Đơn hàng để xác định một phạm vi số thứ tự (được bao gồm trong khóa phân cụm) từ một chỉ mục không được nhóm trên cột ngày:

Gói truy vấn MIN / MAX

Sau đó, bạn có thể Hợp nhất Tham gia hai bảng trên các chỉ mục được nhóm tương ứng của chúng:

Hợp nhất Tham gia đơn hàng và đặt hàng

Rõ ràng, tôi không có dữ liệu của bạn để kiểm tra, nhưng tôi tưởng tượng đây sẽ là một giải pháp thực sự hiệu quả.


+1 Tôi thực sự thích cách suy nghĩ ở đây. Nó không hoạt động cho trường hợp cụ thể này nhưng tôi có ý tưởng.
Marcello Miorelli
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.