การลบระเบียนช้าเมื่อเปิดใช้งานทริกเกอร์


17

ความคิดนี้ได้รับการแก้ไขด้วยลิงก์ด้านล่าง - งานแก้ไข - แต่โปรแกรมแก้ไขไม่ได้ ทำงานกับฝ่ายสนับสนุนของ Microsoft เพื่อแก้ไขปัญหา

http://support.microsoft.com/kb/2606883

ตกลงดังนั้นฉันมีปัญหาที่ฉันต้องการจะออกไป StackOverflow เพื่อดูว่ามีใครมีความคิด

หมายเหตุนี่คือกับ SQL Server 2008 R2

ปัญหา: การลบ 3000 รายการจากตารางที่มีระเบียน 15000 รายการใช้เวลา 3-4 นาทีเมื่อเปิดใช้งานทริกเกอร์และเพียง 3-5 วินาทีเมื่อปิดการใช้งานทริกเกอร์

การตั้งค่าตาราง

เราจะเรียกสองโต๊ะหลักและรอง มัธยมศึกษามีบันทึกรายการที่ฉันต้องการลบดังนั้นเมื่อฉันลบฉันเข้าร่วมในตารางรอง กระบวนการทำงานก่อนคำสั่งลบเพื่อเติมข้อมูลตารางรองที่มีระเบียนที่จะถูกลบ

ลบคำชี้แจง:

DELETE FROM MAIN 
WHERE ID IN (
   SELECT Secondary.ValueInt1 
   FROM Secondary 
   WHERE SECONDARY.GUID = '9FFD2C8DD3864EA7B78DA22B2ED572D7'
);

ตารางนี้มีคอลัมน์จำนวนมากและดัชนี NC แตกต่างกันประมาณ 14 รายการ ฉันลองสิ่งที่แตกต่างกันก่อนที่จะพิจารณาว่ามีปัญหา

  • เปิดการล็อกหน้า (เราปิดใช้งานตามค่าเริ่มต้น)
  • สถิติที่รวบรวมได้ด้วยตนเอง
  • ปิดใช้งานการรวบรวมสถิติโดยอัตโนมัติ
  • ดัชนีการตรวจสอบสุขภาพและการกระจายตัว
  • ลบดัชนีคลัสเตอร์จากตาราง
  • ตรวจสอบแผนการดำเนินการ (ไม่มีอะไรแสดงว่าเป็นดัชนีที่ขาดหายไปและค่าใช้จ่าย 70 เปอร์เซ็นต์สำหรับการลบจริงโดยมีประมาณ 28 เปอร์เซ็นต์สำหรับการเข้าร่วม / รวมบันทึก

ทริกเกอร์

ตารางมี 3 ทริกเกอร์ (หนึ่งรายการสำหรับการดำเนินการแทรกอัปเดตและลบ) ฉันแก้ไขโค้ดเพื่อให้ทริกเกอร์ลบเพิ่งกลับมาจากนั้นเลือกหนึ่งอันเพื่อดูว่ามีการใช้งานกี่ครั้ง จะยิงเพียงครั้งเดียวในระหว่างการดำเนินการทั้งหมด (ตามที่คาดไว้)

ALTER TRIGGER [dbo].[TR_MAIN_RD] ON [dbo].[MAIN]
            AFTER DELETE
            AS  
                SELECT 1
                RETURN

เพื่อสรุป

  • ด้วย Trigger on - statement ใช้เวลาประมาณ 3-4 นาที
  • ด้วย Trigger off - คำสั่งใช้เวลา 3-5 วินาทีจึงจะเสร็จสมบูรณ์

ใครมีความคิดเห็นเกี่ยวกับสาเหตุใด

หมายเหตุ - ไม่ต้องการเปลี่ยนสถาปัตยกรรมนี้เพิ่มลบดัชนี ฯลฯ เป็นวิธีแก้ปัญหา ตารางนี้เป็นส่วนที่สำคัญสำหรับการดำเนินการข้อมูลที่สำคัญบางอย่างและเราต้องปรับแต่งและปรับแต่ง (ดัชนีการล็อกหน้าและอื่น ๆ ) เพื่อให้การดำเนินการพร้อมกันที่สำคัญสามารถทำงานได้โดยไม่ต้องหยุดชะงัก

นี่คือแผนการดำเนินการ xml (ชื่อถูกเปลี่ยนเพื่อปกป้องผู้บริสุทธิ์)

<?xml version="1.0" encoding="utf-16"?>
<ShowPlanXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Version="1.1" Build="10.50.1790.0" xmlns="http://schemas.microsoft.com/sqlserver/2004/07/showplan">
  <BatchSequence>
    <Batch>
      <Statements>
        <StmtSimple StatementCompId="1" StatementEstRows="185.624" StatementId="1" StatementOptmLevel="FULL" StatementOptmEarlyAbortReason="GoodEnoughPlanFound" StatementSubTreeCost="0.42706" StatementText="DELETE FROM MAIN WHERE ID IN (SELECT Secondary.ValueInt1 FROM Secondary WHERE Secondary.SetTMGUID = '9DDD2C8DD3864EA7B78DA22B2ED572D7')" StatementType="DELETE" QueryHash="0xAEA68D887C4092A1" QueryPlanHash="0x78164F2EEF16B857">
          <StatementSetOptions ANSI_NULLS="true" ANSI_PADDING="true" ANSI_WARNINGS="true" ARITHABORT="false" CONCAT_NULL_YIELDS_NULL="true" NUMERIC_ROUNDABORT="false" QUOTED_IDENTIFIER="true" />
          <QueryPlan CachedPlanSize="48" CompileTime="20" CompileCPU="20" CompileMemory="520">
            <RelOp AvgRowSize="9" EstimateCPU="0.00259874" EstimateIO="0.296614" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="185.624" LogicalOp="Delete" NodeId="0" Parallel="false" PhysicalOp="Clustered Index Delete" EstimatedTotalSubtreeCost="0.42706">
              <OutputList />
              <Update WithUnorderedPrefetch="true" DMLRequestSort="false">
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_02]" IndexKind="Clustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[PK_MAIN_ID]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[UK_MAIN_01]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_03]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_04]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_05]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_06]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_07]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_08]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_09]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_10]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_11]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[UK_MAIN_12]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_13]" IndexKind="NonClustered" />
                <RelOp AvgRowSize="15" EstimateCPU="1.85624E-05" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="185.624" LogicalOp="Top" NodeId="2" Parallel="false" PhysicalOp="Top" EstimatedTotalSubtreeCost="0.127848">
                  <OutputList>
                    <ColumnReference Column="Uniq1002" />
                    <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Column="RelationshipID" />
                  </OutputList>
                  <Top RowCount="true" IsPercent="false" WithTies="false">
                    <TopExpression>
                      <ScalarOperator ScalarString="(0)">
                        <Const ConstValue="(0)" />
                      </ScalarOperator>
                    </TopExpression>
                    <RelOp AvgRowSize="15" EstimateCPU="0.0458347" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="185.624" LogicalOp="Left Semi Join" NodeId="3" Parallel="false" PhysicalOp="Merge Join" EstimatedTotalSubtreeCost="0.12783">
                      <OutputList>
                        <ColumnReference Column="Uniq1002" />
                        <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Column="RelationshipID" />
                      </OutputList>
                      <Merge ManyToMany="false">
                        <InnerSideJoinColumns>
                          <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[Secondary]" Column="ValueInt1" />
                        </InnerSideJoinColumns>
                        <OuterSideJoinColumns>
                          <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Column="ID" />
                        </OuterSideJoinColumns>
                        <Residual>
                          <ScalarOperator ScalarString="[MyDatabase].[dbo].[MAIN].[ID]=[MyDatabase].[dbo].[Secondary].[ValueInt1]">
                            <Compare CompareOp="EQ">
                              <ScalarOperator>
                                <Identifier>
                                  <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Column="ID" />
                                </Identifier>
                              </ScalarOperator>
                              <ScalarOperator>
                                <Identifier>
                                  <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[Secondary]" Column="ValueInt1" />
                                </Identifier>
                              </ScalarOperator>
                            </Compare>
                          </ScalarOperator>
                        </Residual>
                        <RelOp AvgRowSize="19" EstimateCPU="0.0174567" EstimateIO="0.0305324" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="15727" LogicalOp="Index Scan" NodeId="4" Parallel="false" PhysicalOp="Index Scan" EstimatedTotalSubtreeCost="0.0479891" TableCardinality="15727">
                          <OutputList>
                            <ColumnReference Column="Uniq1002" />
                            <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Column="ID" />
                            <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Column="RelationshipID" />
                          </OutputList>
                          <IndexScan Ordered="true" ScanDirection="FORWARD" ForcedIndex="false" ForceSeek="false" NoExpandHint="false">
                            <DefinedValues>
                              <DefinedValue>
                                <ColumnReference Column="Uniq1002" />
                              </DefinedValue>
                              <DefinedValue>
                                <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Column="ID" />
                              </DefinedValue>
                              <DefinedValue>
                                <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Column="RelationshipID" />
                              </DefinedValue>
                            </DefinedValues>
                            <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[PK_MAIN_ID]" IndexKind="NonClustered" />
                          </IndexScan>
                        </RelOp>
                        <RelOp AvgRowSize="11" EstimateCPU="0.00392288" EstimateIO="0.03008" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="3423.53" LogicalOp="Index Seek" NodeId="5" Parallel="false" PhysicalOp="Index Seek" EstimatedTotalSubtreeCost="0.0340029" TableCardinality="171775">
                          <OutputList>
                            <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[Secondary]" Column="ValueInt1" />
                          </OutputList>
                          <IndexScan Ordered="true" ScanDirection="FORWARD" ForcedIndex="false" ForceSeek="false" NoExpandHint="false">
                            <DefinedValues>
                              <DefinedValue>
                                <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[Secondary]" Column="ValueInt1" />
                              </DefinedValue>
                            </DefinedValues>
                            <Object Database="[MyDatabase]" Schema="[dbo]" Table="[Secondary]" Index="[IX_Secondary_01]" IndexKind="NonClustered" />
                            <SeekPredicates>
                              <SeekPredicateNew>
                                <SeekKeys>
                                  <Prefix ScanType="EQ">
                                    <RangeColumns>
                                      <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[Secondary]" Column="SetTMGUID" />
                                    </RangeColumns>
                                    <RangeExpressions>
                                      <ScalarOperator ScalarString="'9DDD2C8DD3864EA7B78DA22B2ED572D7'">
                                        <Const ConstValue="'9DDD2C8DD3864EA7B78DA22B2ED572D7'" />
                                      </ScalarOperator>
                                    </RangeExpressions>
                                  </Prefix>
                                </SeekKeys>
                              </SeekPredicateNew>
                            </SeekPredicates>
                          </IndexScan>
                        </RelOp>
                      </Merge>
                    </RelOp>
                  </Top>
                </RelOp>
              </Update>
            </RelOp>
          </QueryPlan>
        </StmtSimple>
      </Statements>
    </Batch>
  </BatchSequence>
</ShowPlanXML>

คำตอบ:


12

กรอบแถวเวอร์ชันแนะนำใน SQL Server 2005 จะใช้ในการรองรับจำนวนของคุณสมบัติรวมทั้งระดับแยกธุรกรรมใหม่และREAD_COMMITTED_SNAPSHOT SNAPSHOTแม้ว่าจะไม่เปิดใช้งานระดับการแยกเหล่านี้การกำหนดเวอร์ชันแถวยังคงใช้สำหรับAFTERทริกเกอร์ (เพื่ออำนวยความสะดวกในการสร้างinsertedและdeletedตารางหลอก), MARS และ (ในที่เก็บเวอร์ชันแยกต่างหาก) การจัดทำดัชนีออนไลน์

ดังที่มีการบันทึกไว้แล้วเอ็นจิ้นอาจเพิ่ม postfix ขนาด 14 ไบต์ให้กับแต่ละแถวของตารางที่มีเวอร์ชันสำหรับวัตถุประสงค์ใด ๆ เหล่านี้ ลักษณะการทำงานนี้ค่อนข้างรู้จักกันดีเนื่องจากเป็นการเพิ่มข้อมูล 14 ไบต์ให้กับทุกแถวของดัชนีที่สร้างขึ้นมาใหม่แบบออนไลน์โดยเปิดใช้ระดับการแยกการกำหนดเวอร์ชันแถว แม้ว่าจะไม่ได้เปิดใช้งานระดับการแยก แต่จะเพิ่มหนึ่งไบต์พิเศษลงในดัชนีที่ไม่ได้ทำคลัสเตอร์เมื่อสร้างขึ้นมาใหม่ONLINEเท่านั้น

ในกรณีที่มีทริกเกอร์ AFTER ปรากฏอยู่และการกำหนดเวอร์ชันจะเพิ่ม 14 ไบต์ต่อแถวการเพิ่มประสิทธิภาพจะมีอยู่ภายในเอ็นจิ้นเพื่อหลีกเลี่ยงปัญหานี้ แต่ไม่สามารถเกิดการจัดสรรROW_OVERFLOWหรือการLOBจัดสรรได้ ในทางปฏิบัติหมายความว่าขนาดสูงสุดที่เป็นไปได้ของแถวต้องน้อยกว่า 8060 ไบต์ ในการคำนวณสูงสุดขนาดแถวที่เป็นไปได้จินสันนิษฐานว่าคอลัมน์ VARCHAR (460) สามารถมีอักขระได้ถึง 460 ตัว

พฤติกรรมนั้นง่ายที่สุดที่จะเห็นด้วยAFTER UPDATEทริกเกอร์แม้ว่าจะใช้หลักการเดียวกันนี้AFTER DELETEก็ตาม สคริปต์ต่อไปนี้สร้างตารางที่มีความยาวสูงสุดในแถว 8060 ไบต์ ข้อมูลพอดีกับหน้าเดียวโดยมีพื้นที่ว่าง 13 ไบต์ในหน้านั้น ไม่มีทริกเกอร์อยู่จึงมีการแยกหน้าและเพิ่มข้อมูลเวอร์ชัน:

USE Sandpit;
GO
CREATE TABLE dbo.Example
(
    ID          integer NOT NULL IDENTITY(1,1),
    Value       integer NOT NULL,
    Padding1    char(42) NULL,
    Padding2    varchar(8000) NULL,

    CONSTRAINT PK_Example_ID
    PRIMARY KEY CLUSTERED (ID)
);
GO
WITH
    N1 AS (SELECT 1 AS n UNION ALL SELECT 1),
    N2 AS (SELECT L.n FROM N1 AS L CROSS JOIN N1 AS R),
    N3 AS (SELECT L.n FROM N2 AS L CROSS JOIN N2 AS R),
    N4 AS (SELECT L.n FROM N3 AS L CROSS JOIN N3 AS R)
INSERT TOP (137) dbo.Example
    (Value)
SELECT
    ROW_NUMBER() OVER (ORDER BY (SELECT 0))
FROM N4;
GO
ALTER INDEX PK_Example_ID 
ON dbo.Example 
REBUILD WITH (FILLFACTOR = 100);
GO
SELECT
    ddips.index_type_desc,
    ddips.alloc_unit_type_desc,
    ddips.index_level,
    ddips.page_count,
    ddips.record_count,
    ddips.max_record_size_in_bytes
FROM sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID(N'dbo.Example', N'U'), 1, 1, 'DETAILED') AS ddips
WHERE
    ddips.index_level = 0;
GO
CREATE TRIGGER ExampleTrigger
ON dbo.Example
AFTER DELETE, UPDATE
AS RETURN;
GO
UPDATE dbo.Example
SET Value = -Value
WHERE ID = 1;
GO
SELECT
    ddips.index_type_desc,
    ddips.alloc_unit_type_desc,
    ddips.index_level,
    ddips.page_count,
    ddips.record_count,
    ddips.max_record_size_in_bytes
FROM sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID(N'dbo.Example', N'U'), 1, 1, 'DETAILED') AS ddips
WHERE
    ddips.index_level = 0;
GO
DROP TABLE dbo.Example;

สคริปต์สร้างผลลัพธ์ที่แสดงด้านล่าง ตารางหน้าเดียวแบ่งออกเป็นสองหน้าและความยาวแถวฟิสิคัลสูงสุดเพิ่มขึ้นจาก 57 เป็น 71 ไบต์ (= +14 ไบต์สำหรับข้อมูลการกำหนดเวอร์ชันแถว)

อัปเดตตัวอย่าง

DBCC PAGEแสดงให้เห็นว่าแถวเดียวมีการปรับปรุงRecord Attributes = NULL_BITMAP VERSIONING_INFO Record Size = 71ในขณะที่แถวอื่น ๆ Record Attributes = NULL_BITMAP; record Size = 57ทั้งหมดในตารางมี

สคริปต์เดียวกันUPDATEโดยแทนที่ด้วยแถวเดียวDELETEจะสร้างเอาต์พุตที่แสดง:

DELETE dbo.Example
WHERE ID = 1;

ลบตัวอย่าง

มีจำนวนแถวน้อยลงหนึ่งแถว (แน่นอน!) แต่ขนาดแถวจริงสูงสุดไม่เพิ่มขึ้น ข้อมูลการกำหนดเวอร์ชันของแถวจะถูกเพิ่มไปยังแถวที่จำเป็นสำหรับทริกเกอร์หลอกตารางเท่านั้นและแถวนั้นจะถูกลบในที่สุด อย่างไรก็ตามการแบ่งหน้ายังคงอยู่ กิจกรรมการแยกหน้านี้รับผิดชอบการทำงานช้าที่สังเกตได้เมื่อมีการเรียก ถ้าความหมายของPadding2คอลัมน์ที่มีการเปลี่ยนแปลงจากvarchar(8000)ที่จะvarchar(7999)หน้าไม่แยกอีกต่อไป

ดูโพสต์บล็อกนี้โดย SQL Server MVP Dmitri Korotkevitch ซึ่งกล่าวถึงผลกระทบต่อการกระจายตัว


1
อ่าฉันถามคำถามเกี่ยวกับเรื่องนี้เมื่อไม่นานมานี้และไม่เคยได้รับคำตอบที่ชัดเจนเลย
Martin Smith

5

นี่คือคำตอบอย่างเป็นทางการจาก Microsoft ... ซึ่งฉันคิดว่าเป็นข้อบกพร่องในการออกแบบที่สำคัญ

11/14/2011 - การตอบสนองอย่างเป็นทางการมีการเปลี่ยนแปลง พวกเขาไม่ได้ใช้บันทึกธุรกรรมตามที่ระบุไว้ก่อนหน้านี้ กำลังใช้ร้านค้าภายใน (ระดับแถว) เพื่อคัดลอกข้อมูลที่เปลี่ยนแปลงไป พวกเขายังไม่สามารถระบุได้ว่าทำไมจึงใช้เวลานาน

เราตัดสินใจที่จะใช้แทนทริกเกอร์แทนทริกเกอร์ลบหลังจาก

ส่วนหลังของทริกเกอร์ทำให้เราต้องอ่านบันทึกการทำธุรกรรมหลังจากการลบเสร็จสมบูรณ์และสร้างตารางแทรก / ลบทริกเกอร์ นี่คือที่ที่เราใช้เวลามากมายและโดยการออกแบบสำหรับส่วนหลังของทริกเกอร์ ทริกเกอร์ INSTEAD OF จะป้องกันพฤติกรรมการสแกนบันทึกธุรกรรมและสร้างตารางที่ถูกแทรก / ลบ นอกจากนี้ตามที่พบว่าสิ่งต่าง ๆ เร็วขึ้นมากถ้าเราวางคอลัมน์ทั้งหมดด้วย nvarchar (สูงสุด) ซึ่งสมเหตุสมผลเนื่องจากข้อเท็จจริงที่ถือว่าเป็นข้อมูล LOB โปรดอ่านบทความด้านล่างเพื่อรับข้อมูลเพิ่มเติมเกี่ยวกับข้อมูล In-Row:

http://msdn.microsoft.com/en-us/library/ms189087.aspx

สรุป: ทริกเกอร์ AFTER ต้องสแกนกลับผ่านบันทึกธุรกรรมหลังจากลบเสร็จแล้วเราต้องสร้างและแทรก / ลบตารางซึ่งต้องการการใช้งานเพิ่มเติมของบันทึกธุรกรรมและเวลา

ดังนั้นในแผนปฏิบัติการนี่คือสิ่งที่เราแนะนำในเวลานี้:

A) Limit the number of rows deleted in each transaction or
B) Increase timeout settings or
C) Don't use AFTER trigger or trigger at all or
D) Limit usage of nvarchar(max) datatypes.

2

ตามแผนทุกอย่างดำเนินไปอย่างถูกต้อง คุณสามารถลองเขียนการลบเป็นเข้าร่วมแทนที่จะเป็นในซึ่งจะให้แผนแตกต่างกัน

DELETE m
FROM MAIN m
JOIN Secondary s ON m.ID = s.ValueInt1
AND s.SetTMGUID = '9DDD2C8DD3864EA7B78DA22B2ED572D7'

ฉันไม่แน่ใจว่าจะช่วยได้อย่างไร เมื่อการลบทำงานด้วยทริกเกอร์บนตารางประเภทการรอสำหรับเซสชันที่ทำการลบคืออะไร?

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.