حلول إدراج أو تحديث SQL Server
-
01-07-2019 - |
سؤال
تفترض بنية الجدول من 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)
كثير من الناس سوف نقترح عليك استخدام 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 أو الخيط 2 نجح مع إدراج.E. g.الموضوع: 1
موضوع آخر فشل مع إدراج (مع خطأ مفتاح مكرر) - الموضوع 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 يعالج هذا.
إذا كنت ترغب في التعامل معها نفسك, هذه هي الطريقة:
تأكد من أن هناك قيد مفتاح أساسي على عمود المفتاح.
ثم:
- هل التحديث
- إذا كان التحديث فشل لأنه سجل مع مفتاح موجود بالفعل ، هل إدراج.إذا كان التحديث لا تفشل الانتهاء.
يمكنك أيضا أن تفعل ذلك العكس ، أيلا إدراج الأولى ، هل التحديث إذا كان إدراج فشل.عادة الطريقة الأولى هي الأفضل ، لأن التحديثات تتم في أكثر الأحيان إدراج.
تفعل إذا كان موجود ...آخر ...ينطوي على القيام طلبين الحد الأدنى (واحدة للتحقق من واحد إلى اتخاذ إجراءات).الطريقة التالية لا يتطلب سوى واحد حيث سجل موجود اثنين إذا كان إدراج مطلوب:
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)
الآن, أنا لا اتبع بلدي المشورة في كثير من الأحيان, لذلك أعتبر مع حبة الملح.
لا تحديد ، إذا كنت تحصل على النتيجة, التحديث, إذا لا إنشاء.