سؤال

تفترض بنية الجدول من MyTable(KEY, datafield1, datafield2...).

غالبا ما أريد تحديث سجل موجود ، أو إدراج سجل جديد إذا لم يكن موجودا.

أساسا:

IF (key exists)
  run update command
ELSE
  run insert command

ما هو أداء أفضل طريقة لكتابة هذا ؟

هل كانت مفيدة؟

المحلول

لا ننسى المعاملات.أداء جيد ، ولكن بسيطة (إذا كان موجود..) نهج خطير جدا.
عندما مواضيع متعددة سوف محاولة تنفيذ إدراج أو تحديث يمكنك بسهولة الحصول على المفتاح الأساسي انتهاك.

الحلول التي تقدمها @العاشق كروفورد & @استيبان عرض فكرة عامة ولكن عرضة للخطأ.

لتجنب المآزق PK انتهاكات يمكنك استخدام شيء من هذا القبيل:

begin tran
if exists (select * from table with (updlock,serializable) where key = @key)
begin
   update table set ...
   where key = @key
end
else
begin
   insert into table (key, ...)
   values (@key, ...)
end
commit tran

أو

begin tran
   update table with (serializable) set ...
   where key = @key

   if @@rowcount = 0
   begin
      insert into table (key, ...) values (@key,..)
   end
commit tran

نصائح أخرى

انظر بلدي إجابة مفصلة جدا غرار السؤال السابق

@العاشق كروفورد هو وسيلة جيدة في SQL 2005 و أدناه ، على الرغم من إذا كنت منح مندوب يجب أن تذهب إلى أول شخص لذلك.المشكلة الوحيدة هي أن إدراج انها لا تزال اثنين IO العمليات.

MS Sql2008 يدخل merge من SQL:2003 standard:

merge tablename with(HOLDLOCK) as target
using (values ('new value', 'different value'))
    as source (field1, field2)
    on target.idfield = 7
when matched then
    update
    set field1 = source.field1,
        field2 = source.field2,
        ...
when not matched then
    insert ( idfield, field1, field2, ... )
    values ( 7,  source.field1, source.field2, ... )

الآن انها حقا واحدة فقط IO العملية ، ولكن فظيعة code :-(

لا UPSERT:

UPDATE MyTable SET FieldA=@FieldA WHERE Key=@Key

IF @@ROWCOUNT = 0
   INSERT INTO MyTable (FieldA) VALUES (@FieldA)

http://en.wikipedia.org/wiki/Upsert

كثير من الناس سوف نقترح عليك استخدام MERGE, لكنني أحذرك ضدها.بشكل افتراضي, هذا لا يحميك من التزامن و شروط السباق أكثر من أي بيانات متعددة ، ولكنه أعرض مخاطر أخرى:

http://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/

حتى مع هذا "أبسط" بناء الجملة المتاحة, أنا لا تزال تفضل هذا النهج (معالجة الخطأ حذف الإيجاز):

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
UPDATE dbo.table SET ... WHERE PK = @PK;
IF @@ROWCOUNT = 0
BEGIN
  INSERT dbo.table(PK, ...) SELECT @PK, ...;
END
COMMIT TRANSACTION;

الكثير من الناس سوف تشير إلى هذا الطريق:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
IF EXISTS (SELECT 1 FROM dbo.table WHERE PK = @PK)
BEGIN
  UPDATE ...
END
ELSE
  INSERT ...
END
COMMIT TRANSACTION;

ولكن كل هذا يحقق ضمان قد تحتاج إلى قراءة الجدول مرتين لتحديد صف(s) إلى تحديث.في العينة الأولى, سوف تحتاج من أي وقت مضى لتحديد صف(s) مرة واحدة.(في كلتا الحالتين, إذا تم العثور على أية صفوف من الأولي قراءة إدراج يحدث.)

الآخرين سوف تشير إلى هذه الطريقة:

BEGIN TRY
  INSERT ...
END TRY
BEGIN CATCH
  IF ERROR_NUMBER() = 2627
    UPDATE ...
END CATCH

ومع ذلك ، هذه ليست مشكلة إذا كان لأي سبب من ترك SQL Server قبض الاستثناءات التي يمكن أن تمنع في المقام الأول هو أكثر تكلفة بكثير ، إلا في النادر السيناريو حيث تقريبا كل إدراج فشل.لقد أثبت الكثير من هنا:

IF EXISTS (SELECT * FROM [Table] WHERE ID = rowID)
UPDATE [Table] SET propertyOne = propOne, property2 . . .
ELSE
INSERT INTO [Table] (propOne, propTwo . . .)

تحرير:

للأسف, حتى بلدي حساب, يجب أن أعترف الحلول التي تفعل هذا من دون تحديد يبدو أن يكون أفضل لأنها إنجاز المهمة مع واحد أقل الخطوة.

إذا كنت ترغب في UPSERT أكثر من سجل واحد في كل مرة يمكنك استخدام ANSI SQL:2003 DML بيان دمج.

MERGE INTO table_name WITH (HOLDLOCK) USING table_name ON (condition)
WHEN MATCHED THEN UPDATE SET column1 = value1 [, column2 = value2 ...]
WHEN NOT MATCHED THEN INSERT (column1 [, column2 ...]) VALUES (value1 [, value2 ...])

تحقق من محاكاة دمج البيان في SQL Server 2005.

على الرغم من أن في وقت متأخر جدا التعليق على هذا أريد أن أضيف أكثر اكتمالا سبيل المثال باستخدام دمج.

هذه تضاف+تحديث البيانات عادة ما تسمى "Upsert" البيانات يمكن تنفيذها باستخدام دمج في SQL Server.

مثال جيد جدا وتعطى هنا:http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx

ما سبق يفسر تأمين التزامن سيناريوهات كذلك.

سوف يكون نقلا عن نفس المرجع:

ALTER PROCEDURE dbo.Merge_Foo2
      @ID int
AS

SET NOCOUNT, XACT_ABORT ON;

MERGE dbo.Foo2 WITH (HOLDLOCK) AS f
USING (SELECT @ID AS ID) AS new_foo
      ON f.ID = new_foo.ID
WHEN MATCHED THEN
    UPDATE
            SET f.UpdateSpid = @@SPID,
            UpdateTime = SYSDATETIME()
WHEN NOT MATCHED THEN
    INSERT
      (
            ID,
            InsertSpid,
            InsertTime
      )
    VALUES
      (
            new_foo.ID,
            @@SPID,
            SYSDATETIME()
      );

RETURN @@ERROR;
/*
CREATE TABLE ApplicationsDesSocietes (
   id                   INT IDENTITY(0,1)    NOT NULL,
   applicationId        INT                  NOT NULL,
   societeId            INT                  NOT NULL,
   suppression          BIT                  NULL,
   CONSTRAINT PK_APPLICATIONSDESSOCIETES PRIMARY KEY (id)
)
GO
--*/

DECLARE @applicationId INT = 81, @societeId INT = 43, @suppression BIT = 0

MERGE dbo.ApplicationsDesSocietes WITH (HOLDLOCK) AS target
--set the SOURCE table one row
USING (VALUES (@applicationId, @societeId, @suppression))
    AS source (applicationId, societeId, suppression)
    --here goes the ON join condition
    ON target.applicationId = source.applicationId and target.societeId = source.societeId
WHEN MATCHED THEN
    UPDATE
    --place your list of SET here
    SET target.suppression = source.suppression
WHEN NOT MATCHED THEN
    --insert a new line with the SOURCE table one row
    INSERT (applicationId, societeId, suppression)
    VALUES (source.applicationId, source.societeId, source.suppression);
GO

استبدال الجدول و الحقل أسماء كل ما تحتاجه.تأخذ الرعاية من تستخدم على الشرط.ثم تعيين القيمة المناسبة (ونوع) المتغيرات على إعلان خط.

الهتافات.

يمكنك استخدام MERGE بيان هذا البيان تستخدم لادخال البيانات إذا كانت غير موجودة أو التحديث إذا كان موجود.

MERGE INTO Employee AS e
using EmployeeUpdate AS eu
ON e.EmployeeID = eu.EmployeeID`

إذا كان الذهاب التحديث إذا-لا-الصفوف-تحديث ثم إدراج الطريق ، تنظر في القيام أدخل أولا إلى منع حالة سباق (بافتراض عدم التدخل حذف)

INSERT INTO MyTable (Key, FieldA)
   SELECT @Key, @FieldA
   WHERE NOT EXISTS
   (
       SELECT *
       FROM  MyTable
       WHERE Key = @Key
   )
IF @@ROWCOUNT = 0
BEGIN
   UPDATE MyTable
   SET FieldA=@FieldA
   WHERE Key=@Key
   IF @@ROWCOUNT = 0
   ... record was deleted, consider looping to re-run the INSERT, or RAISERROR ...
END

وبصرف النظر عن تجنب سباق الشرط إذا كان في معظم الحالات سوف يسجل موجودة بالفعل ثم هذا سوف يسبب إدراج بالفشل ، وإضاعة وحدة المعالجة المركزية.

باستخدام دمج ربما من الأفضل SQL2008 فصاعدا.

في SQL Server 2008 يمكنك استخدام دمج البيان

الذي يعتمد على نمط الاستخدام.على المرء أن ننظر إلى استخدام الصورة الكبيرة دون الدخول في التفاصيل.على سبيل المثال ، إذا كان نمط الاستخدام 99% التحديثات بعد سجل تم إنشاؤه ، ثم 'UPSERT' هو أفضل حل.

بعد إدراج الأول (ضرب) ، وسوف يكون كل واحد بيان التحديثات بدون شروط أو تحفظات.'أين' حالة على إدراج ضروري وإلا فإنه سيتم إدراج مكررة و لا تريد التعامل مع قفل.

UPDATE <tableName> SET <field>=@field WHERE key=@key;

IF @@ROWCOUNT = 0
BEGIN
   INSERT INTO <tableName> (field)
   SELECT @field
   WHERE NOT EXISTS (select * from tableName where key = @key);
END

MS SQL Server 2008 يدخل دمج البيان الذي أعتقد هو جزء من SQL:2003 القياسية.كما هو مبين انها ليست صفقة كبيرة للتعامل مع صف واحد من الحالات, ولكن عند التعامل مع مجموعات كبيرة من البيانات ، المؤشر ، مع كل مشاكل الأداء التي تأتي جنبا إلى جنب.دمج بيان سوف يكون الكثير من الترحيب ذلك عند التعامل مع مجموعات كبيرة من البيانات.

قبل الجميع يقفز إلى HOLDLOCK-s خوفا من هذه nafarious المستخدمين تشغيل sprocs مباشرة :-) اسمحوا لي أن أشير إلى أن يجب أن تضمن تفرد الجديد PK-s حسب التصميم (الهوية مفاتيح تسلسل المولدات في أوراكل ، الفهارس الفريدة الخارجية ID-s, الاستعلامات التي تغطيها المؤشرات).هذا هو الألف والياء من هذه المسألة.إذا لم يكن لديك أنه لا HOLDLOCK-s الكون سوف يوفر لك وإذا كان لديك هذا ثم أنت لا تحتاج إلى أي شيء يتجاوز UPDLOCK على أول اختيار (أو استخدام التحديث الأول).

Sprocs عادة تشغيل تحت ظروف خاضعة للرقابة ، مع افتراض موثوق المتصل (متوسط المستوى).بمعنى أنه إذا كانت بسيطة upsert نمط (تحديث+insert أو دمج) يرى مكررة PK هذا يعني أن الخلل في منتصف الصف أو الجدول التصميم الجيد أن SQL سوف يصيح خطأ في هذه الحالة رفض تسجيل.وضع HOLDLOCK في هذه الحالة يساوي الأكل الاستثناءات وأخذ يحتمل أن تكون خاطئة في البيانات ، إلى جانب الحد من perf.

وقد قلت ذلك, باستخدام دمج أو تحديث ثم أدخل أسهل على الخادم الخاص بك و أقل خطأ عرضه منذ لم يكن لديك أن نتذكر أن إضافة (UPDLOCK) إلى أولا تحديد.أيضا, إذا كنت تفعل إدراج/التحديثات في دفعات صغيرة تحتاج إلى معرفة البيانات الخاصة بك من أجل أن تقرر ما إذا كانت الصفقة مناسبة أو لا.انها مجرد مجموعة لا علاقة لها سجلات ثم إضافية "يغلف" الصفقة سوف تكون ضارة.

لا شروط السباق يهم حقا إذا كنت أول محاولة تحديث تليها إدراج?دعونا نقول لديك اثنين من المواضيع التي تريد تعيين قيمة مفتاح الرئيسية:

الموضوع: 1:القيمة = 1
الموضوع 2:القيمة = 2

على سبيل المثال حالة السباق السيناريو

  1. الرئيسية لم يتم تعريف
  2. الموضوع: 1 فشل مع التحديث
  3. الموضوع 2 فشل مع التحديث
  4. واحد بالضبط من الموضوع: 1 أو الخيط 2 نجح مع إدراج.E. g.الموضوع: 1
  5. موضوع آخر فشل مع إدراج (مع خطأ مفتاح مكرر) - الموضوع 2.

    • النتيجة:"الأول" من اثنين معالجته إلى إدراج تقرر قيمة.
    • أريد النتيجة:آخر من 2 المواضيع لكتابة البيانات (تحديث أو إدراج) يجب أن تقرر قيمة

ولكن ؛ في بيئة متعددة مؤشرات ترابط نظام التشغيل جدولة تقرر بناء على أمر من الخيط التنفيذ - في السيناريو أعلاه ، حيث لدينا حالة السباق هذه ، كان نظام التشغيل الذي قررت على تسلسل التنفيذ.أي:فمن الخطأ أن نقول أن "الموضوع 1" أو "الموضوع 2" كان "الأولى" من وجهة نظر النظام.

عند وقت التنفيذ قريب جدا من الموضوع 1 الموضوع 2, نتائج السباق الشرط لا يهم.والشرط الوحيد أن يكون هذا واحد من المواضيع ينبغي أن تحدد القيمة الناتجة.

تنفيذ:إذا كان التحديث تليها إدراج النتائج في خطأ "مفتاح مكرر" ، ينبغي أن تعامل على أنها نجاح.

أيضا, ينبغي للمرء بالطبع لا تفترض أبدا أن القيمة في قاعدة البيانات هو نفس قيمة كتبت الماضي.

كنت قد حاولت أدناه الحل بالنسبة لي عندما المتزامنة طلب إدراج بيان يحدث.

begin tran
if exists (select * from table with (updlock,serializable) where key = @key)
begin
   update table set ...
   where key = @key
end
else
begin
   insert table (key, ...)
   values (@key, ...)
end
commit tran

يمكنك استخدام هذا الاستعلام.تعمل في جميع إصدارات SQL Server.انها بسيطة و واضحة.ولكن كنت بحاجة إلى استخدام 2 الاستعلامات.يمكنك استخدامها إذا كنت لا يمكن استخدام دمج

    BEGIN TRAN

    UPDATE table
    SET Id = @ID, Description = @Description
    WHERE Id = @Id

    INSERT INTO table(Id, Description)
    SELECT @Id, @Description
    WHERE NOT EXISTS (SELECT NULL FROM table WHERE Id = @Id)

    COMMIT TRAN

ملاحظة:يرجى توضيح الإجابة السلبيات

إذا كنت تستخدم ADO.NET ، DataAdapter يعالج هذا.

إذا كنت ترغب في التعامل معها نفسك, هذه هي الطريقة:

تأكد من أن هناك قيد مفتاح أساسي على عمود المفتاح.

ثم:

  1. هل التحديث
  2. إذا كان التحديث فشل لأنه سجل مع مفتاح موجود بالفعل ، هل إدراج.إذا كان التحديث لا تفشل الانتهاء.

يمكنك أيضا أن تفعل ذلك العكس ، أيلا إدراج الأولى ، هل التحديث إذا كان إدراج فشل.عادة الطريقة الأولى هي الأفضل ، لأن التحديثات تتم في أكثر الأحيان إدراج.

تفعل إذا كان موجود ...آخر ...ينطوي على القيام طلبين الحد الأدنى (واحدة للتحقق من واحد إلى اتخاذ إجراءات).الطريقة التالية لا يتطلب سوى واحد حيث سجل موجود اثنين إذا كان إدراج مطلوب:

DECLARE @RowExists bit
SET @RowExists = 0
UPDATE MyTable SET DataField1 = 'xxx', @RowExists = 1 WHERE Key = 123
IF @RowExists = 0
  INSERT INTO MyTable (Key, DataField1) VALUES (123, 'xxx')

أنا عادة ما عدة ملصقات أخرى قد قال فيما يتعلق التحقق من ذلك القائمة أولا ثم القيام بكل ما في تصحيح المسار.شيء واحد يجب أن نتذكر عند القيام بذلك أن خطة التنفيذ مؤقتا من قبل sql يمكن nonoptimal على مسار واحد أو آخر.أعتقد أن أفضل طريقة للقيام بذلك هو استدعاء اثنين من مختلف الإجراءات المخزنة.

FirstSP:
If Exists
   Call SecondSP (UpdateProc)
Else
   Call ThirdSP (InsertProc)

الآن, أنا لا اتبع بلدي المشورة في كثير من الأحيان, لذلك أعتبر مع حبة الملح.

لا تحديد ، إذا كنت تحصل على النتيجة, التحديث, إذا لا إنشاء.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top