1. 程式人生 > 其它 >python之路38 SQL注入問題 索引觸發器 事務 儲存過程 函式 流程控制

python之路38 SQL注入問題 索引觸發器 事務 儲存過程 函式 流程控制

SQL注入問題

怪像1:輸對使用者名稱就可以登入成功
怪像2:不需要對的使用者名稱和密碼也可以登入成功
  
SQL注入:利用特殊符號的組合產生特殊的含義 從而避開正常的業務邏輯
select * from userinfo where name='jason'
-- afdfsfsfsdsfsd' and pwd=''
select * from userinfo where name='xyz' or 1=1 --afdfdsfsdf'and pwd=''
針對上述的SQL注入問題 核心在於手動拼接了關鍵資料 交給execute處理即可
sql = "select * from userinfo where name=%s and pwd=%s "
cursor.execute(sql,(username,password))
'''
補充說明
     executemany(sql,[(),(),(),()...])
'''

檢視

檢視就是通過查詢得到的一張虛擬表,然後儲存下來,下次直接使用

create view teacher2course as select * from teacher inner join course on teacher.tid = courese.teacher_id;
1.檢視的表只能用來查詢不能做其他增刪改操作
2.檢視儘量少用 會跟真正的表產生混淆 從而干擾操作者

觸發器

達到某個條件之後自動觸發執行
在MySQL中更加詳細的說明是觸發器:針對表繼續增、刪、改操作能夠自動觸發
主要有六種情況:增前、增後、刪前、刪後、改前、改後
create trigger 觸發器的名字 before/after insert/update/delete on 表名 for each row begin
     sql語句
end
1.觸發器命名有一定的規律
   tri_before_insert_t1
   tri_after_delete_t2
   tri_after_update_t2
2.臨時修改SQL語句的結束符
     因為有些操作中需要使用分號
    
觸發器實際應用
CREATE TABLE cmd (
    id INT PRIMARY KEY auto_increment,
    USER CHAR (32),
    priv CHAR (10),
    cmd CHAR (64),
    sub_time datetime, #提交時間
    success enum ('yes', 'no') #0代表執行失敗
);

CREATE TABLE errlog (
    id INT PRIMARY KEY auto_increment,
    err_cmd CHAR (64),
    err_time datetime
);

delimiter $$  # 將mysql預設的結束符由;換成$$
create trigger tri_after_insert_cmd after insert on cmd for each row
begin
    if NEW.success = 'no' then  # 新記錄都會被MySQL封裝成NEW物件
        insert into errlog(err_cmd,err_time) values(NEW.cmd,NEW.sub_time);
    end if;
end $$
delimiter ;  # 結束之後記得再改回來,不然後面結束符就都是$$了

#往表cmd中插入記錄,觸發觸發器,根據IF的條件決定是否插入錯誤日誌
INSERT INTO cmd (
    USER,
    priv,
    cmd,
    sub_time,
    success
)
VALUES
    ('kevin','0755','ls -l /etc',NOW(),'yes'),
    ('kevin','0755','cat /etc/passwd',NOW(),'no'),
    ('kevin','0755','useradd xxx',NOW(),'no'),
    ('kevin','0755','ps aux',NOW(),'yes');

# 查詢errlog表記錄
select * from errlog;
# 檢視所有的觸發器
show triggers;  #  冒號加\G 把資料整理
# 刪除觸發器
drop trigger tri_after_insert_cmd;

事務

事務的四大特性(ACID)
A:原子性
    事務中得到各項操作是不可分割的整體 要麼同時成功要麼同時失敗
C:一致性
    使資料庫從一個一致性狀態變到另一個一致性狀態
I:隔離性
    多個事務之間彼此不干擾
D:永續性
    也稱永久性,指一個事務一旦提交,它對資料庫中資料的改變就應該是永久性的

create table user(
id int primary key auto_increment,
name char(32),
balance int
);

insert into user(name,balance)
values
('jason',1000),
('kevin',1000),
('tank',1000);

# 修改資料之前先開啟事務操作
start transaction;

# 修改操作
update user set balance=900 where name='jason'; #買支付100元
update user set balance=1010 where name='kevin'; #中介拿走10元
update user set balance=1090 where name='tank'; #賣家拿到90元

# 回滾到上一個狀態
rollback;

# 開啟事務之後,只要沒有執行commit操作,資料其實都沒有真正重新整理到硬碟
commit;

"""
事務相關關鍵字
     start transaction;
     rollback
     commit
     savepoint
"""
在SQL標準中定義了四種隔離級別,每一張級別都規定了一個事務中所做的修改
InnoDB支援所有隔離級別
    set transaction isolation level 級別
1.read uncommitted(未提交讀)
    事務中的修改即使沒有提交,對其他事務也都是可見的,事務可以讀取未提交的資料,這一現象也稱之為"髒讀"
2.read committed(提交讀)
    大多數資料庫系統預設的隔離級別
  一個事務從開始直到提交之前所作的任何修改對其他事務都是不可見的,這種級別也叫做"不可重複讀"
3.repeatable read(可重複讀)   # MySQL預設隔離級別
   能夠解決"髒讀"問題,但是無法解決"幻讀"
 所謂幻讀指的是當某個事務在讀取某個範圍內的記錄時另外一個事務又在該範圍內插入了新的記錄,當InnoDB和XtraDB通過多個版本併發控制(MVCC)及間隙鎖策略解決該問題
4.serializable(可序列讀)
   強制事務序列執行,很少使用該級別
    
MVCC只能在read committed(提交讀)、repeatable  read(可重複讀)兩種隔離級別下工作,其他兩個不相容(read uncommitted:總是讀取最新serializable:所有的行都加鎖)

InnoDB的MVCC通過在每行記錄後面儲存兩個隱藏的列來實現MVCC
    一個列儲存了行的建立時間
    一個列儲存了行的過期時間(或刪除時間) # 本質是系統版本號
每開始一個新的事務版本號都會自動遞增,事務開始時刻的系統版本號會作為事務的版本號用來和查詢到的每行記錄版本號進行比較

例如
剛插入第一條資料的時候,我們預設事務id為1,實際是這樣儲存的
    username		create_version		delete_version
    jason		1
可以看到,我們在content列插入了kobe這條資料,在create_version這列儲存了1,1是這次插入操作的事務id。
然後我們將jason修改為jason01,實際儲存是這樣的
    username		create_version		delete_version
    jason		1		2
    jason01			2
可以看到,update的時候,會先將之前的資料delete_version標記為當前新的事務id,也就是2,然後將新資料寫入,將新資料的create_version標記為新的事務id
當我們刪除資料的時候,實際儲存是這樣的
    username		create_version		delete_version
    jason01		2		 3  
    
"""
由此當我門查詢一條記錄的時候,只有滿足以下兩個條件的記錄才會唄顯示處理:
  1.當前事務id要大於或者等於當前行的create_version值,這表示在事務開始前這行資料已經存在了。
  2.當前事務id要小於delete_cersion值,這表示在事務開始之後這行記錄才被刪除。
"""
資料庫三大正規化:
第一正規化(1NF):屬性不可分割,即每個屬性都是不可分割的原子項。(實體的屬性即表中的列)

第二正規化(2NF):滿足第一正規化;且不存在部分依賴,即非主屬性必須完全依賴於主屬性。(主屬性即主鍵;完全依賴是針對於聯合主鍵的情況,非主鍵列不能只依賴於主鍵的一部分)

第三正規化(3NF):滿足第二正規化;且不存在傳遞依賴,即非主屬性不能與非主屬性之間有依賴關係,非主屬性必須直接依賴於主屬性,不能間接依賴主屬性。(A -> B, B ->C, A -> C)

儲存過程

可以可看成是python中的自定義函式
# 無參函式
delimiter  $$
create procrdure p1()
begin
    select * from cmd;
end $$
delimiter;
# 呼叫
call p1()

# 有參函式
delimiter $$

create procedure p2(
   in m int,  # in 表示這個引數必須只能是傳入不能被返回出去
   in n int, 
   out res int,  # out表示這個引數可以被返回出去,還有一個inout表示即可以傳入也可以被返回出去
)
begin
   select * from amd where id>m and id <n;
set res=0;  # 用來標誌儲存過程是否執行
end $$
delimiter ;

# 針對 res需要先提前定義
set @res=10;  定義
select @res;  檢視
call p1(1,5,@res)  呼叫
select @res  檢視

"""
檢視儲存過程具體資訊
    show create procedure pro1;
檢視所有儲存過程
    show procedure status;
刪除儲存過程
    drop procedure  pro1;
"""
# 大前提:儲存過程在哪個庫下面建立只能在對應的庫下面才能使用!!!
# 1、直接在mysql中呼叫
set @res=10  # res的值是用來判斷儲存過程是否被執行成功的依據,所以需要先定義一個變數@res儲存10
call p1(2,4,10);  # 報錯
call p1(2,4,@res);

# 檢視結果
select @ res;  # 執行成功,@res變數值發生了變化

# 2、在python程式中呼叫
pymysqli連結mysql
產生的遊標cursor.callproc('p1',(2,4,10))  # 內部原理:@_p1_0=2,@_p1_1=4,@_p1_2=10;
cursor.excute('select @_p1_2;')

函式

可以看成是python中的內建函式
"ps:可以通過help 函式名   檢視幫助資訊!!"
# 1.移除指定字元
Trim、LTRim、RTrim

# 2.大小寫轉換
Lower、Upper

# 3.獲取左右起始指定個數字元
Left、Right

# 返回讀音相似值(對英文效果)
Soundex
"""
eg:客戶表中有一個顧客登記的使用者名稱為	J.lee
      但如果這是輸入錯誤真名其實叫J.Lie,可以使用soundex(name)匹配發音類似的 where Soundex(name)=Soundex(J.Lie)
"""
# 5.日期格式:date_format
'''在MySQL中表示時間格式儘量採用2022-11-11形式'''
CREATE TABLE blog (
    id INT PRIMARY KEY auto_increment,
    NAME CHAR (32),
    sub_time datetime
);
INSERT INTO blog (NAME, sub_time)
VALUES
    ('第1篇','2015-03-01 11:31:21'),
    ('第2篇','2015-03-11 16:31:21'),
    ('第3篇','2016-07-01 10:21:31'),
    ('第4篇','2016-07-22 09:23:21'),
    ('第5篇','2016-07-23 10:11:11'),
    ('第6篇','2016-07-25 11:21:31'),
    ('第7篇','2017-03-01 15:33:21'),
    ('第8篇','2017-03-01 17:32:21'),
    ('第9篇','2017-03-01 18:31:21');
select date_format(sub_time,'%Y-%m'),count(id) from blog group by date_format(sub_time,'%Y-%m');

1.where Date(sub_time) = '2015-03-01'
2.where Year(sub_time)=2016 AND Month(sub_time)=07;
# 更多日期處理相關函式 
	adddate	增加一個日期 
	addtime	增加一個時間
	datediff計算兩個日期差值

流程控制

# 分支結構
declare i int default 0;
IF i = 1 THEN
	SELECT 1;
ELSEIF i = 2 THEN
	SELECT 2;
ELSE
	SELECT 7;
END IF;
# 迴圈結構
DECLARE num INT ;
SET num = 0 ;
WHILE num < 10 DO
	SELECT num ;
	SET num = num + 1 ;
END WHILE ;

索引相關概念

1)索引就好比一本書的目錄,它能讓你更快的找到自己想要的內容
2)讓獲取的資料更有目的性,從而提高資料庫檢索資料的效能

索引在MySQL中也叫做"鍵",是儲存引擎用於快速找到記錄的一種資料結構
   * ptimary key
   * unique key
   * index key
1.上述的三個key都可以加快資料查詢
2.primary key 和unique key 除了可以加快查詢本身還自帶限制條件而index key很單一就是用來加快資料查詢
3.外來鍵不屬於索引鍵的範圍 是用來建立關係的 與加快查詢無關

索引加快查詢的本質
   id int primary key auto_increment,
   name varcher(32) ubique,
   proince varcher(32)
   age int 
   phone bigint
    
   select name from userinfo where phone=188188888888;  # 一頁頁的翻
    select name from userinfo where id=99999;  # 按照目錄確定頁數找
索引可以加快資料查詢 但是會降低增刪的速度
通常情況下我們頻繁使用某些欄位查詢資料
    為了提示查詢的速度可以將該欄位建立索引

聚集索引(primary key)
   主鍵、主鍵索引
輔助索引(unique,index)
   除主鍵以外的都是輔助索引
覆蓋索引
   select name from user where name='jason';
非覆蓋索引
   select age from user where name= 'jason';

索引資料結構

索引底層其實是樹結構>>>:樹是計算機底層的資料結構
 
樹有很多中型別
	二叉樹、b樹、b+樹、B*樹......

二叉樹
	二叉樹裡面還可以細分成很多領域 我們簡單的瞭解即可 
  	二叉意味著每個節點最大隻能分兩個子節點
B樹
	所有的節點都可以存放完整的資料
B+\*樹
	只有葉子節點才會存放真正的資料 其他節點只存放索引資料
 	B+葉子節點增加了指向其他葉子節點的指標
  	B*葉子節點和枝節點都有指向其他節點的指標

輔助索引在查詢資料的時候最會還是需要藉助於聚集索引
	輔助索引葉子節點存放的是資料的主鍵值

有時候就算採用索引欄位查詢資料 也可能不會走索引!!!
	最好能記三個左右的特殊情況

慢查詢優化

explain

1)index      儘量避免
2)range
3)ref
4)eq_ref
5)const
6)system
7)null

索引

準備:
#1. 準備表
create table s1(
id int,
name varchar(20),
gender char(6),
email varchar(50)
);

#2. 建立儲存過程,實現批量插入記錄
delimiter $$ #宣告儲存過程的結束符號為$$
create procedure auto_insert1()
BEGIN
    declare i int default 1;
    while(i<3000000)do
        insert into s1 values(i,'jason','male',concat('jason',i,'@oldboy'));
        set i=i+1;
    end while;
END$$ #$$結束
delimiter ; #重新宣告分號為結束符號

#3. 檢視儲存過程
show create procedure auto_insert1\G 

#4. 呼叫儲存過程
call auto_insert1();

測試索引

# 表沒有任何索引的情況下
select * from s1 where id=30000;
# 避免列印帶來的時間損耗
select count(id) from s1 where id = 30000;
select count(id) from s1 where id = 1;

# 給id做一個主鍵
alter table s1 add primary key(id);  # 速度很慢

select count(id) from s1 where id = 1;  # 速度相較於未建索引之前兩者差著數量級
select count(id) from s1 where name = 'jason'  # 速度仍然很慢


"""
範圍問題
"""
# 並不是加了索引,以後查詢的時候按照這個欄位速度就一定快   
select count(id) from s1 where id > 1;  # 速度相較於id = 1慢了很多
select count(id) from s1 where id >1 and id < 3;
select count(id) from s1 where id > 1 and id < 10000;
select count(id) from s1 where id != 3;

alter table s1 drop primary key;  # 刪除主鍵 單獨再來研究name欄位
select count(id) from s1 where name = 'jason';  # 又慢了

create index idx_name on s1(name);  # 給s1表的name欄位建立索引
select count(id) from s1 where name = 'jason'  # 仍然很慢!!!
"""
再來看b+樹的原理,資料需要區分度比較高,而我們這張表全是jason,根本無法區分
那這個樹其實就建成了“一根棍子”
"""
select count(id) from s1 where name = 'xxx';  
# 這個會很快,我就是一根棍,第一個不匹配直接不需要再往下走了
select count(id) from s1 where name like 'xxx';
select count(id) from s1 where name like 'xxx%';
select count(id) from s1 where name like '%xxx';  # 慢 最左匹配特性

# 區分度低的欄位不能建索引
drop index idx_name on s1;

# 給id欄位建普通的索引
create index idx_id on s1(id);
select count(id) from s1 where id = 3;  # 快了
select count(id) from s1 where id*12 = 3;  # 慢了  索引的欄位一定不要參與計算

drop index idx_id on s1;
select count(id) from s1 where name='jason' and gender = 'male' and id = 3 and email = 'xxx';
# 針對上面這種連續多個and的操作,mysql會從左到右先找區分度比較高的索引欄位,先將整體範圍降下來再去比較其他條件
create index idx_name on s1(name);
select count(id) from s1 where name='jason' and gender = 'male' and id = 3 and email = 'xxx';  # 並沒有加速

drop index idx_name on s1;
# 給name,gender這種區分度不高的欄位加上索引並不難加快查詢速度

create index idx_id on s1(id);
select count(id) from s1 where name='jason' and gender = 'male' and id = 3 and email = 'xxx';  # 快了  先通過id已經講資料快速鎖定成了一條了
select count(id) from s1 where name='jason' and gender = 'male' and id > 3 and email = 'xxx';  # 慢了  基於id查出來的資料仍然很多,然後還要去比較其他欄位

drop index idx_id on s1

create index idx_email on s1(email);
select count(id) from s1 where name='jason' and gender = 'male' and id > 3 and email = 'xxx';  # 快 通過email欄位一劍封喉 

聯合索引

select count(id) from s1 where name='jason' and gender = 'male' and id > 3 and email = 'xxx';  
# 如果上述四個欄位區分度都很高,那給誰建都能加速查詢
# 給email加然而不用email欄位
select count(id) from s1 where name='jason' and gender = 'male' and id > 3; 
# 給name加然而不用name欄位
select count(id) from s1 where gender = 'male' and id > 3; 
# 給gender加然而不用gender欄位
select count(id) from s1 where id > 3; 

# 帶來的問題是所有的欄位都建了索引然而都沒有用到,還需要花費四次建立的時間
create index idx_all on s1(email,name,gender,id);  # 最左匹配原則,區分度高的往左放
select count(id) from s1 where name='jason' and gender = 'male' and id > 3 and email = 'xxx';  # 速度變快

慢查詢日誌

設定一個時間檢測所有超出該時間的sql語句,然後針對性的進行優化!

全文索引

MySQL的全文檢索功能MYISAM儲存引擎支援而InnoDB儲存引擎不支援
一般在建立表的時候啟用全文檢索功能
create table t1(
	id int primary key auto_increment,
  content text
	fulltext(content)
)engine=MyISAM;

# match括號內的值必須是fulltext括號中定義的(單個或者多個)
select content from t1 where match(content) against('jason')
'''上述語句可以用like實現但是查詢出來的結果順序不同 全文檢索會以文字匹配的良好程度排序資料再返回效果更佳'''

# 查詢擴充套件
select note_text from productnotes where Math(note_text) Against('jason' with query expansion);
"""
返回除jason外以及其他jason所在行相關文字內容行資料
eg:
	jason is handsome and cool,every one want to be cool,tony want to be more handsome;
	二三句雖然沒有jason關鍵字 但是含有jason所在行的cool和handsome
"""

# 布林文字搜尋
即使沒有定義fulltext也可以使用,但是這種方式非常緩慢效能低下
select note_text from productnotes where Match(note_text) Against('jason' in boolean mode);

# 注意事項
1.三個及三個以下字元的詞視為短詞,全文檢索直接忽略且從索引中排除
2.MySQL自身自帶一個非用詞列表,表內詞預設均被忽略(可以修改該列表)
3.出現頻率高於50%的詞自動作為非用詞忽略,該規則不適用於布林搜尋
4.針對待搜尋的文字內容不能少於三行,否則檢索不返回任何結果
5.單引號預設忽略

插入資料

資料庫經常被多個使用者訪問,insert操作可能會很耗時(特別是有很多索引需要更新的時候)而且還可能降低等待處理的select語句效能
如果資料檢索是最重要的(一般都是),則可以通過在insert與into之間新增關鍵字low_priority指示MySQL降低insert語句優先順序
	insert low_priority  into 
  
insert還可以將一條select語句的結果插入表中即資料匯入:insert select
eg:想從custnew表中合併資料到customers表中
  insert into customers(contact,email) select contact,email from custnew;

更新資料

如果使用update語句更新多列值,並且在更新這些列中的一列或者多列出現一個錯誤會導致整個update操作被取消,如果想發生錯誤也能繼續執行沒有錯誤的更新操作可以採用
	update ignore custmoers ...
  """
  update ignore  set name='jason1',id='a' where id=1;
  	name欄位正常修改
  update set name='jason2',id='h' where id=1;
  	全部更新失敗
  """

刪除資料

delete語句從表中刪除資料,甚至可以是所有資料但是不會刪除表本身
並且如果想從表中刪除所有的行不要使用delete可以使用truncate速度更快並且會重置主鍵值(實際是刪除原來的表並重新建立一個表而不是逐行刪除表中的資料)

主鍵

檢視當前表主鍵自增到的值(表當前主鍵值減一)
	select last_insert_id();

外來鍵

MySQL儲存引擎可以混用,但是外來鍵不能跨引擎即使用一個引擎的表不能引用具有使用不同引擎表的外來鍵

重命名錶

rename關鍵字可以修改一個或者多個表名    rename table customer1 to customer2;  rename table back_cust to b_cust,                         back_cust1 to b_cust1,                         back_cust2 to b_cust2;

事務

MySQL提供兩種事務型儲存引擎InnoDB和NDB cluster及第三方XtraDB、PBXT

事務處理中有幾個關鍵詞彙會反覆出現
  事務(transaction)
  回退(rollback)
  提交(commit)
  保留點(savepoint)
   為了支援回退部分事務處理,必須能在事務處理塊中合適的位置放置佔位符,這樣如果需要回退可以回退到某個佔位符(保留點)
    建立佔位符可以使用savepoint
    	savepoint sp01;
    回退到佔位符地址
    	rollback to sp01;
    # 保留點在執行rollback或者commit之後自動釋放

安全管理

1.建立使用者
	create user 使用者名稱 identified by '密碼';
 	"""修改密碼"""
  	set password for 使用者名稱 = Password('新密碼');
    set password = Password('新密碼');  # 針對當前登入使用者
2.重新命名
	rename user 新使用者名稱 to 舊使用者名稱; 
3.刪除使用者
	drop user 使用者名稱;
4.檢視使用者訪問許可權
	show grants for 使用者名稱;
5.授予訪問許可權
	grant select on db1.* to 使用者名稱;
  # 授予使用者對db1資料庫下所有表使用select許可權
6.撤銷許可權
	revoke select on db1.* from 使用者名稱;
"""
整個伺服器
	grant all/revoke all
整個資料庫
	on db.*
特定的表
	on db.t1
"""

隔離級別

在SQL標準中定義了四種隔離級別,每一種級別都規定了一個事務中所做的修改
InnoDB支援所有隔離級別
	set transaction isolation level 級別

1.read uncommitted(未提交讀)
	事務中的修改即使沒有提交,對其他事務也都是可見的,事務可以讀取未提交的資料,這一現象也稱之為"髒讀"
2.read committed(提交讀)
	大多數資料庫系統預設的隔離級別
  一個事務從開始直到提交之前所作的任何修改對其他事務都是不可見的,這種級別也叫做"不可重複讀"
3.repeatable read(可重複讀)		# MySQL預設隔離級別
	能夠解決"髒讀"問題,但是無法解決"幻讀"
  所謂幻讀指的是當某個事務在讀取某個範圍內的記錄時另外一個事務又在該範圍內插入了新的記錄,當之前的事務再次讀取該範圍的記錄會產生幻行,InnoDB和XtraDB通過多版本併發控制(MVCC)及間隙鎖策略解決該問題
4.serializable(可序列讀)
	強制事務序列執行,很少使用該級別

讀鎖(共享鎖)
	多個使用者同一時刻可以同時讀取同一個資源互不干擾
寫鎖(排他鎖)
	一個寫鎖會阻塞其他的寫鎖和讀鎖
死鎖
	1.多個事務試圖以不同的順序鎖定資源時就可能會產生死鎖
  2.多個事務同時鎖定同一個資源時也會產生死鎖
	# Innodb通過將持有最少行級排他鎖的事務回滾

事務日誌

事務日誌可以幫助提高事務的效率 
	儲存引擎在修改表的資料時只需要修改其記憶體拷貝再把該修改記錄到持久在硬碟上的事務日誌中,而不用每次都將修改的資料本身持久到磁碟
  事務日誌採用的是追加方式因此寫日誌操作是磁碟上一小塊區域內的順序IO而不像隨機IO需要次哦按的多個地方移動磁頭所以採用事務日誌的方式相對來說要快的多
  事務日誌持久之後記憶體中被修改的資料再後臺可以慢慢刷回磁碟,目前大多數儲存引擎都是這樣實現的,通常稱之為"預寫式日誌"修改資料需要寫兩次磁碟

MVCC多版本控制

MVCC只能在read committed(提交讀)、repeatable read(可重複讀)兩種隔離級別下工作,其他兩個不相容(read uncommitted:總是讀取最新  serializable:所有的行都加鎖)

InnoDB的MVCC通過在每行記錄後面儲存兩個隱藏的列來實現MVCC
	一個列儲存了行的建立時間
  一個列儲存了行的過期時間(或刪除時間)  # 本質是系統版本號
每開始一個新的事務版本號都會自動遞增,事務開始時刻的系統版本號會作為事務的版本號用來和查詢到的每行記錄版本號進行比較

例如
剛插入第一條資料的時候,我們預設事務id為1,實際是這樣儲存的
    username		create_version		delete_version
    jason			1
可以看到,我們在content列插入了kobe這條資料,在create_version這列儲存了1,1是這次插入操作的事務id。
然後我們將jason修改為jason01,實際儲存是這樣的
    username		create_version		delete_version
    jason			1				2
    jason01					2
可以看到,update的時候,會先將之前的資料delete_version標記為當前新的事務id,也就是2,然後將新資料寫入,將新資料的create_version標記為新的事務id
當我們刪除資料的時候,實際儲存是這樣的
    username		create_version		delete_version
    jason01		2		 3
"""
由此當我們查詢一條記錄的時候,只有滿足以下兩個條件的記錄才會被顯示出來:
   1.當前事務id要大於或者等於當前行的create_version值,這表示在事務開始前這行資料已經存在了。
   2.當前事務id要小於delete_version值,這表示在事務開始之後這行記錄才被刪除。
"""

轉換表的引擎

主要有三種方式,並各有優缺點!
# 1.alter table
   alter table t1 engine=InnoDB;
"""
    適用於任何儲存引擎 但是需要執行很長時間 MySQL會按行將資料從原表賦值到一張新的表中,在複製期間可能會消耗系統所有的IO能力,同時原表會加讀鎖
"""
# 2.匯入匯出
"""
使用mysqldump工具將資料匯出到檔案,然後修改檔案中相應的SQL語句
    1.引擎選項
    2.表名
""" 
# 3.insert ... select
"""
綜合了第一種方案的高效和第二種方案的安全
     1.先建立一張新的表
    2.利用insert ... select語法導資料
    資料量不大這樣做非常合適 資料量大可以考慮分批處理 針對每一段資料執行事務提交操作避免產生過多的undo
"""
  ps:上述操作可以使用pt-online-schema-change(基於facebook的線上schema變更技術)工具,簡單方便的執行上述過程