Welcome to Comedown's Blog Technology !
Loading...

Theo dõi hệ thống database (Audit Database)

Người đăng: Unknown Chủ Nhật, 7 tháng 6, 2009 Lượt xem: counter

Trong một số dự án tôi từng trải qua, việc theo dõi lại những hành động đã xảy ra trong cơ sở dữ liệu là một việc làm hết sức quan trọng, giải pháp của nó rất nhiều, khó khăn cũng rất nhiều, hôm nay, tôi giới thiệu một cách tiếp cận khá đơn giản mà cực kỳ hiệu quả, nếu bài viết này có ích với bạn, xin đừng ngần ngại đóng góp ý kiến của bạn dưới bài viết này.

Bạn sẽ theo dõi những thay đổi trong database như thế nào, khi người dùng xóa, sửa dữ liệu. Bạn sẽ có một vài cách tiếp cận sau:

* Tạo ra một cột tên là isDeleted:

Thoạt nhìn phải công nhận ý tưởng này rất tốt, bất cứ khi nào dữ liệu trên cột bị xóa nó sẽ không xóa bỏ hoàn toàn mà chỉ đánh dấu mà thôi, cách giải quyết này sẽ giải quyết được vấn đề delete, tuy nhiên nó vấp phải vấn đề về ràng buộc dữ liệu. Hãy tưởng tượng tôi có một bảng username tôi sẽ tổ chức như sau:

ID-UserName-Password và cột isDeleted. Và bạn đã hiểu chuyện gì trong này USERNAME phải là duy nhất trong hệ thống. Nó chỉ được đang ký lại khi một người đã hủy nó đi hoặc chưa tồn tại.


Bây giờ tôi xóa username =xyz, nghĩa là username =xyz là isDeleted, sau đó tôi tiếp tục insert username là xyz.

Lúc này vấn đề tôi đã phải ràng buộc toàn vẹn trên database là nằm trên cột isDeleted, Constraint của tôi phải ràng buộc username và isDeleted là duy nhất, tuyệt, nhưng riêng trong chuyện này thế đã là không ổn, bạn đã phải tính tới chuyện tạo một constrain cho một cột không tham gia vào bussiness của hệ thống, điều này lẽ ra nên tránh.

Mặt khác, chuyện gì sẽ xảy ra nếu tôi insert username=zyz, sau đó xóa, rồi tạo lại, rồi lại xóa.

Vấn đề bây giờ bạn phải luôn kiểm tra trước khi insert dữ liệu, có bao giờ bạn tự hỏi, vậy constraint trong database đã sinh ra để làm gì không??


* Tạo một bản sao database:

Nếu đã làm qua Oracle bạn đều biết có một loại audit table mà oracle hỗ trợ để quản lý việc insert, delete , update. Không nhất thiết phải Oracle, trong database khác bạn cũng có thể dễ dàng cài đặt chức năng này, đơn giản như sau:

Tạo một Database log y hệt database gốc, mỗi bảng thêm một cột là action cho update, delete (insert là tùy chọn của bạn)

Tạo trigger cho từng bảng, khi có thay đổi trên database gốc, nó sẽ insert vào bảng log với sự kiện tượng ứng.

Cách giải quyết này theo tôi là rất tốt: thứ nhất nó không làm nặng nề database gốc của chúng ta, khi dữ liệu bị xóa đi, nó sẽ chuyển sang database log và không làm phình to database gốc và dễ hiểu như thế khi truy vấn database gốc sẽ cho tốc độ tốt hơn vì ít dữ liệu hơn.

Vấn đề của nó là khó quản lý, bạn phải viết chương trình quản lý cho từng bảng, cực đấy chứ nhỉ.


* Sử dụng một bảng duy nhất làm bảng Audit.

Đoạn script để tạo bảng này như sau:

CREATE TABLE Audit (
Type CHAR(1),
TableName VARCHAR(128),
PK VARCHAR(1000),
FieldName VARCHAR(128),
OldValue VARCHAR(1000),
NewValue VARCHAR(1000),
UpdateDate datetime,
UserName VARCHAR(128))

Với cách tiếp cận này, tôi sẽ giải thích các field như sau:

* AuditID :là một id tự tăng.
* Type: một action nó có thể là D (Delete) I (Insert) U (Update).
* TableName : action xảy ra trên bảng nào.
* PrimaryKeyField : khóa chính của dòng bị xóa (với bảng 1 khóa chính -Theo Agile, nếu bạn mong muốn khác đi, hãy customize code)
* PrimaryKeyValue: giá trị của cột chứa khóa chính.
* FieldName : Cột bị xảy ra action.
* OldValue : Giá trị cũ trước khi bị thay đổi.
* NewValue : Giá trị mới sau khi bị thay đổi.
* UpdateDate : Ngày giờ xảy ra action.
* UserName : người dùng (Tôi sẽ sử dụng user của hệ thống, hãy sử dụng username trên một table khác như bạn muốn)

Nhìn vào bảng kết quả chắc bạn đã hình dung được vấn đề.

-- Set up the tables
-- Firstly, we create the audit table.
-- There will only need to be one of these in a database

IF NOT EXISTS (SELECT * FROM sysobjects WHERE id = OBJECT_ID(N'[dbo].[Audit]')
AND OBJECTPROPERTY(id, N'IsUserTable') = 1)
CREATE TABLE Audit
(Type CHAR(1),
TableName VARCHAR(128),
PK VARCHAR(1000),
FieldName VARCHAR(128),
OldValue VARCHAR(1000),
NewValue VARCHAR(1000),
UpdateDate datetime,
UserName VARCHAR(128))
GO

-- now we will illustrate the use of this tool
-- by creating a dummy test table called TrigTest.

IF EXISTS (SELECT * FROM sysobjects WHERE id = OBJECT_ID(N'[dbo].[trigtest]')
AND OBJECTPROPERTY(id, N'IsUserTable') = 1)
DROP TABLE [dbo].[trigtest]
GO
CREATE TABLE trigtest
(i INT NOT NULL,
j INT NOT NULL,
s VARCHAR(10),
t VARCHAR(10))
GO

--note that for this system to work there must be a primary key to the table
--but then a table without a primary key isn't really a table is it?
ALTER TABLE trigtest ADD CONSTRAINT pk PRIMARY KEY (i, j)
GO

--and now create the trigger itself. This has to be created for every
-table you want to monitor

CREATE TRIGGER tr_trigtest ON trigtest FOR INSERT, UPDATE, DELETE
AS

DECLARE @bit INT ,
@field INT ,
@maxfield INT ,
@char INT ,
@fieldname VARCHAR(128) ,
@TableName VARCHAR(128) ,
@PKCols VARCHAR(1000) ,
@sql VARCHAR(2000),
@UpdateDate VARCHAR(21) ,
@UserName VARCHAR(128) ,
@Type CHAR(1) ,
@PKSelect VARCHAR(1000)

--You will need to change @TableName to match the table to be audited
SELECT @TableName = 'trigtest'

-- date and user
SELECT @UserName = SYSTEM_USER ,
@UpdateDate = CONVERT(VARCHAR(8), GETDATE(), 112)
+ ' ' + CONVERT(VARCHAR(12), GETDATE(), 114)

-- Action
IF EXISTS (SELECT * FROM inserted)
IF EXISTS (SELECT * FROM deleted)
SELECT @Type = 'U'
ELSE
SELECT @Type = 'I'
ELSE
SELECT @Type = 'D'

-- get list of columns
SELECT * INTO #ins FROM inserted
SELECT * INTO #del FROM deleted

-- Get primary key columns for full outer join
SELECT @PKCols = COALESCE(@PKCols + ' and', ' on')
+ ' i.' + c.COLUMN_NAME + ' = d.' + c.COLUMN_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk ,

INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
WHERE pk.TABLE_NAME = @TableName
AND CONSTRAINT_TYPE = 'PRIMARY KEY'
AND c.TABLE_NAME = pk.TABLE_NAME
AND c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME

-- Get primary key select for insert
SELECT @PKSelect = COALESCE(@PKSelect+'+','')
+ '''<' + COLUMN_NAME + '=''+convert(varchar(100), coalesce(i.' + COLUMN_NAME +',d.' + COLUMN_NAME + '))+''>'''
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk ,
INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
WHERE pk.TABLE_NAME = @TableName
AND CONSTRAINT_TYPE = 'PRIMARY KEY'
AND c.TABLE_NAME = pk.TABLE_NAME
AND c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME

IF @PKCols IS NULL
BEGIN
RAISERROR('no PK on table %s', 16, -1, @TableName)
RETURN
END

SELECT @field = 0,
@maxfield = MAX(ORDINAL_POSITION)
FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = @TableName
WHILE @field < @maxfield BEGIN SELECT @field = MIN(ORDINAL_POSITION) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = @TableName AND ORDINAL_POSITION > @field
SELECT @bit = (@field - 1 )% 8 + 1
SELECT @bit = POWER(2,@bit - 1)
SELECT @char = ((@field - 1) / 8) + 1
IF SUBSTRING(COLUMNS_UPDATED(),@char, 1) & @bit > 0 OR @Type IN ('I','D')
BEGIN
SELECT @fieldname = COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = @TableName
AND ORDINAL_POSITION = @field
SELECT @sql = '
insert Audit ( Type,
TableName,
PK,
FieldName,
OldValue,
NewValue,
UpdateDate,
UserName)
select ''' + @Type + ''','''
+ @TableName + ''',' + @PKSelect
+ ',''' + @fieldname + ''''
+ ',convert(varchar(1000),d.' + @fieldname + ')'
+ ',convert(varchar(1000),i.' + @fieldname + ')'
+ ',''' + @UpdateDate + ''''
+ ',''' + @UserName + ''''
+ ' from #ins i full outer join #del d'
+ @PKCols
+ ' where i.' + @fieldname + ' <> d.' + @fieldname
+ ' or (i.' + @fieldname + ' is null and d.' + @fieldname + ' is not null)'
+ ' or (i.' + @fieldname + ' is not null and d.' + @fieldname + ' is null)'
EXEC (@sql)
END
END

GO

-------------------------------------------------------
Đoạn mã trên sẽ làm việc hoàn hảo, nó sẽ đi vào sơ đồ của hệ thống và tìm ra tất cả những bảng có trong schema, sau đó tạo từng trigger theo một template nhất định- Hãy giới hạn table hay column bằng cách customize lại code này.

Lưu ý: Đoạn mã này thực hiện trên Microsoft SQL Server và sử dụng trigger hãy sửa đổi cho phù hợp trên những database khác. Điều này không thể thực hiện trên CSDL không hỗ trợ trigger.

Lợi ích : tiếp cận thông qua chỉ một table, điều này mang đến sự thuận tiện và dễ dàng khi quản trị, nếu hệ thống tiếp tục sinh sôi ra các bảng, đó không phải là vấn đề.

Bất lợi : Một chút về vấn đề Perfomance, với các Database trung bình và nhỏ, việc audit là bình thường, tuy nhiên nếu database lớn khi sử dụng nhiều câu Insert và Delete sẽ tạo ra những dữ liệu khổng lổ trên từng dòng (vì nó lưu 1 field trên một dòng audit ).

Trong mọi loại database dù lớn hay nhỏ, nếu chỉ sử dụng để tracking Update action, đây là một cách tiếp cận tốt nhất. Với Delete, hãy customize lại mã để sử dụng tối thiểu trường cần phải tracking hoặc có thể áp dụng phương pháp logging thứ 2 dựa trên đoạn mã này.


Nguồn: http://dangtritue.blogspot.com/2008/11/theo-di-h-thng-database-audit-database.html

0 nhận xét

Đăng nhận xét

1. Vui lòng nhập tiếng việt có dấu.
2. Không sử dụng từ ngữ thô tục, trái với thuần phong mỹ tục.
3. Nội dung phải liên quan tới chủ đề bài viết, bạn có thể sử dụng một số thẻ HTML như <b>, <i>, <a>
4. Những nhận xét mang tính chất Đồi trụy, Quảng cáo, Spam hoặc không có tính xây dựng... sẽ bị xóa vĩnh viễn mà không cần báo trước.

Bài đăng Mới hơn Bài đăng Cũ hơn Trang chủ
Blogumulus by Roy Tanck and Amanda FazaniInstalled by CahayaBiru.com

Followers

Cộng đồng Blogger

Comedown's Blog
TruongGTGR - Nơi siêu xe hội tụ
Tư vấn tin học