1. 程式人生 > >資料庫(六)—— 資料庫安全與保護

資料庫(六)—— 資料庫安全與保護

一、資料庫完整性

        資料完整性約束是為了防止資料庫中存在不符合語義的資料,為了維護資料的完整性,加在資料庫資料之上的語義約束條件就是資料完整性約束,而DBMS檢查是否滿足完整性約束條件的機制就稱為完整性檢查。

1、完整性約束條件的作用物件

        完整性檢查是圍繞完整性約束條件進行的,因而完整性約束條件是完整性檢查機制的核心。完整性約束條件的作用物件可以是列、元組和表。

⑴ 列級約束

        列級約束主要是指對列的型別、取值範圍、精度等的約束,具體包括以下內容:

① 對資料型別的約束;

② 對資料格式的約束;

③ 對取值範圍或取值集合的約束;

④ 對空值的約束。

⑵ 元組約束

        元組約束指無級中各個欄位之間的相互約束。

⑶ 表級約束

        表級約束指若干元組之間、關係之間的聯絡的約束。

2、定義與實現完整性約束

        關係模型的完整性規則是對關係的某種約束條件,關係模型中可以有三類完整性約束,分別是實體完整性、參照完整性和使用者定義的完整性。

⑴ 實體完整性

       在MYSQL中,實體完整性是通過主鍵約束和候選鍵約束來實現的。

① 主鍵約束

主鍵可以是表中某一列,也可以是表中多個列所構成的一個組合,多個列組合而成的主鍵也被稱為複合主鍵。主鍵必須遵守以下規則:

a. 每一個表只能定義一個主鍵;

b. 鍵值必須能夠唯一標識表中的每一行記錄,且不能為NULL(唯一性原則);

c. 複合主鍵不能包含不必要的多餘列(最小化原則);

d. 一個列名在複合主鍵的列表中只能出現一次。

        主鍵約束使用關鍵字PRIMARY KEY來實現,其實現方式有兩種:一種是作為列的完整性約束,直接在列定義後面加上關鍵字;一種是作用表的完整性約束,在所有列定義語句之後新增一條主鍵約束語句。複合主鍵只能使用第二種方式。定義主鍵約束後,MYSQL會自動為主鍵建立一個唯一性索引,該索引名預設為PRIMARY,也可以重新命名。

        示例如下:

/*列級主鍵約束,在列定義之後新增主鍵約束關鍵字*/
create table user
(
	oid int not null auto_increment primary key comment '自增主鍵',
	username varchar(50) not null comment '使用者名稱',
	pwd varchar(100) not null comment '使用者密碼'
)ENGINE=INNODB;
/*表級主鍵約束,在所有列定義之後新增主鍵約束語句*/
create table user
(
	oid int not null auto_increment comment '自增主鍵',
	username varchar(50) not null comment '使用者名稱',
	pwd varchar(100) not null comment '使用者密碼',
  primary key (oid)
)ENGINE=INNODB;
/*表級主鍵約束,新增複合主鍵約束語句*/
create table user
(
	username varchar(50) not null comment '使用者名稱',
	pwd varchar(100) not null comment '使用者密碼',
	sex char(2) not null default '男' comment '使用者性別',
  primary key (username,sex)
)ENGINE=INNODB;
/*檢視建立主鍵約束後的user表索引情況*/
show index from user;

② 候選鍵約束

候選鍵與主鍵差不多,它使用關鍵字UNIQUE,它與主鍵的區別是:

a. 一個表中只能定義一個主鍵,但可以定義多個候選鍵;

b. 定義主鍵時,系統會自動產生PRIMARY KEY索引,而定義候選鍵時,會自動產生UNIQUE索引。

        示例如下:

/*列級約束,新增候選鍵約束關鍵字*/
create table user
(
	oid int not null auto_increment primary key comment '自增主鍵',
	username varchar(50) not null unique comment '使用者名稱',
	pwd varchar(100) not null comment '使用者密碼'
)ENGINE=INNODB;
/*表級約束,新增候選鍵約束語句*/
create table user
(
	oid int not null auto_increment primary key comment '自增主鍵',
	username varchar(50) not null comment '使用者名稱',
	pwd varchar(100) not null comment '使用者密碼',
  unique (username)
)ENGINE=INNODB;
/*檢視建立主鍵約束後的user表索引情況*/
show index from user;

⑵ 參照完整性

        參照完整性是通過在建立表或更新表的同時定義一個外來鍵宣告來實現的。外來鍵宣告有兩種方式:一種是直接在列定義之後加上reference_definition語法項;一種是在所有列的屬性定義後面新增FOREIGN KEY (index_column_name,...) reference_definition子句的語法項。在MYSQL中只能表級的外來鍵約束才會生效。

        其中,reference_definition語法項的定義如下:

/*參照完整性的reference_definition語法項*/
REFERENCES tb_name(index_column_name,...)
[ON DELETE reference_option]
[ON UPDATE reference_option]

         index_column_name的語法格式如下:

column_name [(length)] [ASC|DESC]

        reference_option的語法格式如下:

RESTRICT|CASCADE|SET NULL

         在上面的語法中,語法項reference_option用於指定參照完整性約束的實現策略,當沒有明確指出參照完整性的實現策略時,預設使用RESTRICT,它表示當刪除或更新被參照表中的被參照列時,參照表中的參照列拒絕刪除或更新;CASCADE表示級聯策略,當被參照表中刪除或更新記錄時,自動刪除或更新參照表中匹配的記錄行;SET NULL表示置空策略,當被參照表中刪除或更新記錄時,參照表中相關的記錄置空。指定外來鍵時還要遵守以下規則:

① 被參照表必須已經建立(也可以是同一個表,這樣的表被稱為自參照表,這種結構被稱為自參照結構);

② 必須為被參照表定義主鍵;

③ 外來鍵允許出現一個空值,只要外來鍵的每個非空值出現在指定的主鍵中,這個外來鍵的內容就是正確的;

④ 外來鍵中列的數目必須和被參照表中的主鍵列的數目相同;

⑤ 外來鍵中列的資料型別必須和被參照表的主鍵中對應的資料型別相同。

        示例如下:

/*表級參照完整性約束示例,且設定更新刪除時使用級聯策略*/
create table user
(
	oid int not null auto_increment primary key comment '自增主鍵',
	username varchar(50) not null comment '使用者名稱',
	pwd varchar(100) not null comment '使用者密碼',
  ref_oid int not null comment '外來鍵參照emp表的主鍵',
  foreign key(ref_oid) references emp(empno) on delete cascade on update cascade
)ENGINE=INNODB;
/*查看錶的所有外來鍵*/
select * from INFORMATION_SCHEMA.KEY_COLUMN_USAGE 
where REFERENCED_TABLE_NAME = 'emp';

⑶ 使用者定義的完整性

        MYSQL支援使用者自定義完整性約束,分別是非空約束、CHECK約束和觸發器。

① 非空約束

        非空約束是在列定義的後面加上NOT NULL作為限定詞,來約束該列的取值不能為空。

② CHECK約束

CHECK約束允許使用複雜的表示式作為限定條件,比如子查詢,CHECK約束支援列級和表級的限定。但在MYSQL中並不支援CHECK約束的使用,MYSQL儲存引擎會對CHECK語句進行分析但會忽略CHECK語句,這只是為了提高相容性。可以使用enum或觸發器來解決這個問題。使用enum示例如下:

/*使用enum來代替CHECK檢查約束*/
create table user
(
	oid int not null auto_increment primary key comment '自增主鍵',
	sex enum('男','女') not null default '男' comment '使用者性別'
)ENGINE=INNODB;

3、命名完整性約束

       為了刪除和修改完整性約束,首先需要在定義約束的同時對其進行命名,命名完整性約束的方法是在各種完整性約束的定義說明之前加上關鍵字CONSTRAINT和該約束的名字,這個名字在資料庫中必須是唯一的,如果沒有給出該名字,則MYSQL會自動建立一個。示例如下:

/*命名完整性約束示例*/
create table user
(
	oid int not null auto_increment primary key comment '自增主鍵',
	username varchar(50) not null comment '使用者名稱',
	pwd varchar(100) not null comment '使用者密碼',
  ref_oid int not null comment '外來鍵參照emp表的主鍵',
  constraint user_ref_emp foreign key(ref_oid) references emp(empno) on delete cascade on update cascade
)ENGINE=INNODB;

4、更新完整性約束 

        完整性約束不可以直接被修改,若要修改某個完整性約束,實際上是先用ALTER TABLE語句刪除該約束,然後再新增一個與該約束同名的新約束。使用ALTER TABLE語句可以獨立的刪除完整性約束,而不會刪除表本身,若使用DROP TABLE語句刪除一個表,則這個表中的所有完整性約束都會自動被刪除。刪除完整性約束示例如下:

/*刪除外來鍵*/
alter table user drop foreign key user_ref_emp
/*新增外來鍵*/
alter table user
add constraint user_ref_emp foreign key(ref_oid) references emp(empno) on delete cascade on update cascade

二、觸發器

        觸發器是使用者定義在關係表上的一類由事件驅動的資料庫物件,也是一種保證資料完整性的方法。它的作用主要是實現主鍵和外來鍵不能保證的複雜的參照完整性和資料的一致性,從而有效的保護表中的資料。

1、建立觸發器

        建立觸發器的語法如下:

CREATE TRIGGER trigger_name trigger_time trigger_event
ON tb_name FOR EACH ROW trigger_body

         在上面的語法中,trigger_time有兩個可選項,即關鍵字BEFORE和關鍵字AFTER,用於表示觸發器是在啟用它的之前或之後觸發;trigger_event用於指定觸發事件,可以是INSERT、UPDATE、DELETE中的一個。

        注意:每個表中每個事件每次只允許建立一個觸發器,因此每個表最多支援建立6個觸發器。

        示例如下:

create table user
(
	oid int not null auto_increment comment '自增主鍵',
	username varchar(50) not null comment '使用者名稱',
	pwd varchar(100) not null comment '使用者密碼',
	sex char(2) not null default '男' comment '使用者性別',
    age int comment '使用者年齡',
	address varchar(200) comment '使用者住址',
    phone varchar(20) comment '電話',
    primary key (oid)
)ENGINE=INNODB;
/*建立觸發器的示例*/
create trigger tri_user_insert after insert on user for each row set @str = '一個小測試';
/*向user表中插入一條資料*/
insert into user values(null,'zhangsan','pwd_zhangsan','男',20,null,null);
/*查詢使用者變數驗證觸發器*/
select @str

2、刪除觸發器

        刪除觸發器的語法如下:

DROP TRIGGER [IF EXISTS] [schema_name.]trigger_name

       示例如下:

drop trigger if exists demo.tri_user_insert

注意:當刪除一個表的同時,也會刪除這個表上的所有觸發器,如果要修改一個觸發器,必須先刪除它,再重新建立。 

 3、使用觸發器

⑴ INSERT觸發器

        在INSERT觸發器程式碼內,可以引用一個名為NEW的虛擬表,來訪問被插入的行。在BEFORE INSERT觸發器中,NEW中的值也可以被更新,即允許更改被插入的值。示例如下:

/*建立INSERT觸發器*/
create trigger tri_user_insert after insert on user for each row set @str = new.username;
/*向user表中插入一條資料*/
insert into user values(null,'zhangsan','pwd_zhangsan','男',20,null,null);
/*查詢使用者變數驗證觸發器*/
select @str

⑵ DELETE觸發器

        在DELETE觸發器程式碼內,可以引用一個名為OID的虛擬表,來訪問被刪除的行,OLD中的值全部是隻讀的,不能被更新。示例如下:

/*建立DELETE觸發器*/
create trigger tri_user_delete after delete on user for each row set @str = old.oid;
/*刪除user表中的一條資料*/
delete from user where username = 'zhangsan';
/*查詢使用者變數驗證觸發器*/
select @str

⑶ UPDATE觸發器

        在UPDATE觸發器程式碼內,可以引用一個名為OLD的虛擬表訪問以前的值,也可以引用一個名為NEW的虛擬表來訪問更新的值。在BEFORE UPDATE觸發器中,NEW中的值可能也被更新,即允許更改將要用於UPDATE語句中的值。當觸發器涉及對觸發表自身的更新操作時,只能使用BEFORE UPDATE觸發器。示例如下:

/*建立UPDATE觸發器*/
create trigger tri_user_update before update on user for each row set new.pwd = old.username
/*更新user表中的資料*/
update user set pwd = 'wangwu123' where username = 'zhangsan'

三、安全性與訪問控制

1、使用者賬號管理

        檢視MYSQL中現有的使用者,如下:

select user from mysql.user

⑴ 建立使用者賬號

        建立使用者賬號的語法格式如下:

CREATE USER user[IDENTIFIED BY [PASSWORD]'password']

          上面的語法格式中,user指定建立使用者賬號,其格式為'user_name'@'host_name',user_name表示使用者名稱,host_name表示主機名,即連線MYSQL時所在的主機名字。如果沒有指定主機名,則預設表示為“%”。關鍵字PASSWORD用於指定雜湊口令(函式PASSWORD()可以返回密碼的雜湊值),若使用明文設定口令,需忽略PASSWORD關鍵字。示例如下:

/*查詢密碼的雜湊值*/
select password('123');
/*建立兩個使用者,一個使用明文密碼,一個使用雜湊密碼*/
create user 'zhangsan1'@'localhost' identified by '123',
'zhangsan2'@'localhost' identified by password '*23AE809DDACAF96AF0FD78ED04B6A265E05AA257';

注意:如果兩個使用者具有相同的使用者名稱和不同的主機名,MYSQL會將它們視為不同的使用者,並允許為這兩個不同的使用者分配不同的許可權集合。 

⑵ 更改使用者賬號

        其語法格式如下:

RENAME USER old_user TO new_user [,old_user TO new_user]...

         使用示例如下:

rename user 'zhangsan1'@'localhost' to 'wangwu'@'localhost' 

⑶ 修改使用者口令

        其語法格式如下:

SET PASSWORD [FOR user]={PASSWORD('new_password')|'encrypted password'} 

        如果要修改使用者的口令 ,必須將新口令傳遞到PASSWORD()函式中進行加密或者直接使用加密後的口令。可選項FOR子句如果不加,表示修改當前使用者的口令,如果加上的話表示修改指定使用者的口令。示例如下:

set password for 'wangwu'@'localhost' = '*531E182E2F72080AB0740FE2F2D689DBE0146E04'

⑷ 刪除使用者賬號

        刪除使用者賬號的語法格式如下:

DROP USER user [,user]...

       示例如下:

drop user 'wangwu'@'localhost'

2、使用者許可權管理

        建立使用者後,需要為使用者分配適當的許可權,新建立的賬號只能登入MYSQL伺服器,不能執行任何資料庫操作。檢視使用者的授權表如下:

show grants for 'zhangsan2'@'localhost'

⑴ 許可權的授予

        常用的語法格式如下:

GRANT privi_type [(column_list)] [,privi_type [(column_list)]] ...
ON [object_type] privi_level TO user_specification [,user_specification] ...
[WITH GRANT OPTION]

        在上面的語法格式中,語法項privi_type用來指定許可權的名稱,比如SELECT、UPDATE、DELETE等資料庫操作;ON子句用於指定許可權授予的物件和級別; 可選項object_type用於指定許可權授予的物件型別,包括表、函式和儲存過程,分別用關鍵字TABLE、FUNCTION、PROCEDURE來進行標識;語法項privi_level用於指定許可權的級別,*表示當前資料庫中的所有表,*.*表示所有資料庫中的所有表;TO子句用來指定授予許可權的的使用者和口令,已經指定口令的會覆蓋,如果是一個不存在的使用者,則會自動建立使用者;WITH子句用於實現許可權的轉移或限制。

        授予使用者在表中的某些列上進行查詢的許可權,示例如下:

/*授予使用者在表中的某些列上進行查詢的許可權示例*/
grant select(empno,ename) on demo.emp to 'zhangsan2'@'localhost'
/*在zhangsan2賬號下查詢emp中的empno和ename列*/
select empno,ename from demo.emp

        建立一個新的使用者並設定登入口令,同時授予他們在emp表上擁有SELECT和UPDATE的許可權,示例如下:

grant select,update on demo.emp to 'zhangsan3'@'localhost' identified by '123'

        授予使用者可以在資料庫中執行所有操作的許可權,示例如下:

grant all on demo.* to 'zhangsan3'@'localhost'

         授予使用者建立使用者的許可權,示例如下:

grant create user on *.* to 'zhangsan3'@'localhost'

⑵ 許可權的轉移

        WITH子句可以實現許可權的轉移,如果將WITH子句指定為WITH GRANT OPTION,則表示TO子句中所指定的使用者都具有把自己所擁有的許可權授予給其他使用者的權利。示例如下:

/*建立一個新的使用者給其相關的許可權,並允許其將自身的許可權授予其他使用者*/
grant select,update on demo.emp to 'zhangsan4'@'localhost' identified by '123' with grant option

⑶ 許可權的撤銷 

         回收許可權的語法是格式如下:

REVOKE privi_type [(column__list)] [,privi_type[(column_list)]]...
ON [object_type] privi_level FROM user [,user]...

         回收所有許可權的語法格式如下:

REVOKE ALL PRIVILEGES,GRANT OPTION FROM user [,user]...

         回收使用者的SELECT許可權的示例如下:

revoke select on demo.emp from 'zhangsan4'@'localhost'

注意:要使用REVOKE語句,必須要擁有資料庫的全域性CREATE USER許可權或UPDATE許可權。 

四、事務與併發控制

        資料庫中的資料是共享資源,因此資料庫系統通常都是多使用者系統,即支援多個不同程式或同一程式併發地存取資料庫中相同的資料,為了防止它們彼此干擾,從而保證資料庫的正確性不被破壞,避免資料的不一致性,這種機制就被稱為併發機制。其中,事務就是為保證資料的一致性而產生的一個概念和基本手段。

1、事務的概念

        事務是使用者定義的一個數據操作序列,這些操作可作為一個完整的工作單元,要麼全部執行,要麼全部不執行,是一個不可分割的工作單位。使用者顯式定義事務的語句一般有三條:BEGIN TRANSACTION 、COMMIT和ROLLBACK。

2、事務的特徵

        事務具有四個特徵:原子性、一致性、隔離性、永續性。

⑴ 原子性

        事務的原子性保證事務包含的一組操作是原子不可分的,即事務是不可分割的最小工作單元,所包含的這些操作是一個整體,事務在執行時要麼全部成功,要麼全部失敗回滾。

⑵ 一致性

        一致性要求事務必須滿足資料庫的完整性約束,且事務執行完畢後將資料庫由一個一致性狀態轉變到另一個一致性狀態。

⑶ 隔離性

        隔離性要求事務是彼此獨立的、隔離的,即一個事務的執行不能被其他事務所幹擾,一個事務對資料庫變更的結果必須在它提交之後,另一個事務才能存取。

        多個事務併發執行時,其結果應該等價於它們的一種順序執行的結果,就如同序列排程執行事務一樣,這一特性也被稱為可序列性,即系統執行的任何交錯操作排程實質上是一個序列排程,而序列排程是指每當排程一個事務,在該事務的所有操作沒有結束之前其他的事務不能被執行。

⑷ 永續性

        永續性是指一個事務一旦提交,它對資料庫中資料的改變應該是永久性的,且接下來的其他操作或故障不應該對其執行結果有任何影響。

3、併發操作問題

        事務是併發控制的基本單位,併發控制機制就是要用正確的方式排程併發操作,使一個使用者事務的執行不受其他事務的干擾,從而避免造成資料的不一致性。

⑴ 髒讀

        髒讀是指一個事務處理中讀取了另一個事務處理中未提交的資料。

⑵ 不可重複讀

        不可重複讀是指對於資料庫中某個資料,一個事務範圍內多次查詢卻返回了不同的資料值,這是由於在查詢間隔中,被另一個事務修改並提交了,從而無法再重現前一次讀取的結果。

⑶ 幻讀

        幻讀是事務非獨立執行時發生的一種現象,如果一個事務對資料表的中每一行的某個資料項都進行了修改並提交,而另一個事務插入一條未修改的新資料,如果第一事務的使用者檢視剛剛修改的資料,就會發現有一條未修改的資料,好像幻覺一樣,這就是幻讀。

        幻讀和不可重複讀都是讀取了另一個已經提交的事務,不同的是幻讀讀到的是其他事務的新增資料,而不可重複讀讀到的是其他事務的更改資料。

4、四種隔離級別

⑴ Serializable(序列化):可避免髒讀、不可重複讀、幻讀的發生;

⑵ Repeatable read(可重複讀):可避免髒讀、不可重複讀的發生;

⑶ Read commited(讀已提交):可避免髒讀的發生;

⑷ Read uncommited(讀未提交):最低級別,任何情況都無法保證。

        以上四種隔離級別最高的是Serializable級別,最低的是Read uncommitted級別,當然級別越高,執行效率就越低。像Serializable這樣的級別,就是以鎖表的方式(類似於Java多執行緒中的鎖)使得其他的執行緒只能在鎖外等待,所以平時選用何種隔離級別應該根據實際情況。

  在MySQL資料庫中,預設的為Repeatable read (可重複讀);而在Oracle資料庫中,只支援Serializable (序列化)級別和Read committed (讀已提交)這兩種級別,其中預設的為Read committed級別。

關於隔離性還可以參考:

5、封鎖

        封鎖是最常用的併發控制技術,它的基本思想是:需要時,事務通過向系統請求對它所希望的資料物件進行加鎖,以確保它不會被非預期改變。

⑴ 鎖

        一個鎖實質上就是允許或阻止一個事務對一個數據物件的存取特權。一個事務對一個數據物件加鎖的結果就是將別的事務封鎖在該物件之外,防止了其他事務對該物件的變更,而加鎖的事務則可執行它所希望的處理並維持該物件的正確狀態。

        基本的鎖型別有兩種:排他鎖和共享鎖。一般寫操作要求排他鎖,讀操作要求共享鎖。

⑵ 封鎖的粒度

        通常以粒度來描述封鎖的資料單元的大小。DBMS可以決定不同粒度的鎖,鎖住整個資料庫,DBMS的管理和控制最簡單,只需要設定和測試一個鎖,故系統開銷也最小,然而對資料的存取只能順序進行,因而系統的總體系統大大下降;反之,資料元素鎖將提供最多的併發性,但DBMS要設定大量的鎖裝置來標識那些當前被封鎖的資料元素,同時還要大量的鎖檢測,影響了每一個事務的服務效能,系統總體效能也因此而下降。所以,大多高效能的系統都選擇折中的鎖粒度。

⑶ 活鎖與死鎖

        在併發事務處理過程中,由於鎖會使一事務處於等待狀態而排程其他事務處理,因而該事務可能會因為優先順序低而永遠等待下去,這種現象稱為“活鎖”;而兩個以上事務迴圈等待被同組中的另一事務鎖住的資料單元的情形,稱為“死鎖”。

        預防死鎖的辦法有以下幾種:

① 一次性鎖請求

每一事務在處理時一次提出所有的鎖請求,僅當這些請求全部滿足時事務處理才會進行,否則讓其等待。

② 鎖請求排序

將每個資料單元標以線性順序,然後要求每一事務都按此順序提出鎖請求。

③ 序列化處理

通過應用設計為每一資料單元建立一個主程式,對給定資料單元的所有請求都發送給主程式,而主程式以單道的形式執行,系統以多道形式執行。

④ 資源剝奪

每當事務因鎖請求不能滿足而受阻時,強行令兩個衝突的事務中的一個ROLLBACK,釋放所有的鎖,以後再重新執行(有可能會出現活鎖)。

⑤ 死鎖檢測

一旦檢測到系統已發生的死鎖再進行解除處理,死鎖檢測可以以圖論的方法實現,並以正在執行的事務為結點。

⑷ 兩段封鎖法

        兩段封鎖法是事務遵循兩段鎖協議的排程方法,它規定在任何一個事務中,所有加鎖操作都必須在所有釋放鎖操作之前。其中,事務劃分為以下兩個階段:

① 發展或加鎖階段

        在此期間,對任一資料物件進行任一操作之前,事務都要獲得對該物件的一個相應的鎖。

② 收縮或釋放鎖階段

        一旦事務釋放了一個鎖,則標明它已經進入了此階段,此後它就不能再請求任何另外的鎖。

關於兩段鎖有如下定理:

        遵循兩段鎖協議的事務的任何併發排程都是可序列化的。

五、備份與恢復

        資料庫備份是指通過匯出資料或者複製檔案的方式來製作資料庫的複本;資料庫恢復則是當資料庫出現故障或遭到破壞時,將備份的資料庫載入到系統,從而使資料庫從錯誤狀態恢復到備份時的正確狀態。