前言
最近寫了兩個小腳本,一個應用於Mysql的自動填充測試數據,另外一個是bash寫的定期刪除日志文件,兩個腳本如何使用,在GitHub
上面都有所說明,這里不再贅述,這里主要是想聊一下Mysql
的存儲過程
以及自動填充測試數據。
為什么要寫一個自動填充測試數據的腳本?
網上其實也有一些簡單的給Mysql
填充數據的博客,但是大多數都是針對於特定的表的特定數據來實現,寫的過於簡單,實用性不強,而這個腳本可以根據我們提供表的字段,來自動識別我們的字段並填充進入對應的內容,常用的結構都能夠滿足,當然還有進一步完善的空間。
存儲過程
Mysql
的存儲過程可以幫助我們實現一些較為復雜的業務邏輯,就像我們在PHP
或者其他語言中所寫的邏輯一樣,在Mysql
中也同樣可以執行,比如我們要循環寫入1000行數據。
不過雖然Mysql
可以實現,但是我們更希望把業務邏輯建立在我們的業務語言上面,而Mysql
則專注於處理數據的CURD
,其主要原因在於我們今后在修改或者要查找到這塊的業務邏輯時,會相對麻煩一些。
因此,我更傾向於Mysql
可以用來做一些與我們的業務邏輯無關,而又需要用到語言的邏輯性的項目,比如我在前面提到的自動填充數據。
形式
delimiter $$
drop procedure if exists test $$
create procedure test()
begin
-- do something
end $$
delimiter ;
這是一個自動識別表結構並填充數據的腳本,在第一行使用了delimiter $$
用作分隔符,因為接下來的腳本里面很多地方會使用到;
,所以為防止過早的識別結束,暫時修改了Mysql
默認的分隔符。
此外,前面有begin
和end
包裹,用於識別開始和結束。
暫時先不講邏輯怎么實現的(其實邏輯也非常簡單),我們先來了解一下Mysql
的存儲過程的幾個部分。
注:通過show create procedure your_procedure_name
可以查看創建的存儲過程的代碼內容。
變量
Mysql
的變量分成三種:全局變量,用戶變量,局部變量。
- 局部變量
局部變量存在於我們的存儲過程中,在外面無法訪問:
delimiter $$
drop procedure if exists test $$
create procedure test()
begin
declare myName varchar(8) default 'seven';
set myName = 'nine';
select myName;
end $$
delimiter ;
call test();
結果輸出nine
,因為在用戶變量中需要@
來聲明,所以此時在外面無法使用select myName
,否則報錯。
- 用戶變量
用戶變量存在於全局,但是有效期僅限於會話
期(所以也可以叫做會話變量),即我們下次打開Mysql
時,變量就會消失:
delimiter $$
drop procedure if exists test $$
create procedure test()
begin
set @myName = 'nine';
end $$
delimiter ;
call test();
select @myName ;
輸出nine
。
當然,賦值的形式是多樣的,我們也可以結合查詢語句來賦值:
select name, age from user limit 1 into @myName , @myAge;
select @myName , @myAge;
這個時候賦值的內容就是我們表中的數據,這里需要注意的是一一對應關系,切不可一對多或者多對一。
在某些時候,我們可能需要對查詢的次數進行記錄,其實這個時候我們可以完全使用變量來幫我們實現:
set @selectNum = 0;
select * , @selectNum := @selectNum + 1 from user limit 1;
這個時候@selectNum
的結果為2。
- 全局變量
全局變量是設置在系統中的配置,我們可以通過show global variables
來查看,也可以通過set global oneofvariable=value
來設置我們已經存在的配置,這里我特意給已經存在這幾個字眼加粗,因為我在網上看到有博文說可以設置自定義的全局變量,但是我嘗試了之后發現報錯了,我用版本5.5
和5.7
都嘗試過了,提示這個變量不存在。
參數
參數分成IN
,OUT
以及INOUT
三種情況:
其實從其字面上我們也能猜出他們的不同效果:IN
是不會影響外面設置的結果(IN是默認方式),OUT
和INOUT
是會影響到的,同時INOUT
兩邊是相互影響的,我們還是以上面的test
為例:
- in
set @num = 1;
delimiter $$
drop procedure if exists test $$
create procedure test(num int)
begin
set num = 2;
end $$
delimiter ;
call test(@num);
select @num;
結果為1。
- out
set @num = 1;
delimiter $$
drop procedure if exists test $$
create procedure test(OUT num int)
begin
set num = 2;
end $$
delimiter ;
call test(@num);
select @num;
結果為2。
語句塊
這篇文章關於語句塊已經闡述的足夠詳細,這里不再贅述。
實現
代碼:
delimiter $$
drop procedure if exists fillTable $$
create procedure fillTable(in num int , in tbName varchar(16))
begin
-- 獲取當前數據庫
select (@dbName:=database());
set @tbName = tbName;
-- 獲取表的字段總數
set @currSql = "select count(1) from information_schema.COLUMNS where table_name = ? and table_schema = ? into @columnSum";
prepare stmt from @currSql;
execute stmt using @tbName , @dbName;
deallocate prepare stmt;
set @currNum = 0;
-- 這里設置隨機的字符串
set @chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
while @currNum < num do
-- 這里設置sql后面拼接
set @insertSql = concat("insert into " , @tbName , " values ( ");
set @columnNum = 1;
while @columnNum <= @columnSum do
set @value := '';
set @currSql = "select (@column := COLUMN_NAME) , (@length := CHARACTER_MAXIMUM_LENGTH) , (@key := COLUMN_KEY) , (@type := DATA_TYPE) from information_schema.COLUMNS where table_name = ? and table_schema = ? limit ?";
prepare stmt from @currSql;
execute stmt using @tbName , @dbName , @columnNum;
deallocate prepare stmt;
-- 根據類型來填充數據
if right(@type , 3) = 'int' then
if @type = 'int' then
set @value = 'default';
else
set @value = floor(rand() * 100);
end if;
elseif right(@type , 4) = 'char' then
set @counter = 0;
while @counter < @length do
set @value = concat(@value,substr(@chars,ceil(rand()*(length(@chars)-1)),1));
set @counter = @counter + 1;
end while;
set @value = concat("'" , @value , "'");
elseif @type = 'blob' or right(@type , 4) = 'text' then
set @counter = 0;
while @counter < 100 do
set @value = concat(@value,substr(@chars,ceil(rand()*(length(@chars)-1)),1));
set @counter = @counter + 1;
end while;
set @value = concat("'" , @value , "'");
elseif @type = 'float' or @type = 'decimal' then
set @value = round(rand() , 2);
else
set @value = 'nine';
end if;
-- 判斷這個數是否是最后一個
if @columnNum = @columnSum then
set @insertSql = concat(@insertSql , @value , ' )');
else
set @insertSql = concat(@insertSql , @value , ' , ');
end if;
set @columnNum = @columnNum + 1;
end while;
-- 執行
prepare stmt from @insertSql;
execute stmt;
deallocate prepare stmt;
set @currNum = @currNum + 1;
end while;
end $$
delimiter ;
其實實現這個功能的邏輯非常簡單,再各個步驟里面也附上了步驟,主要是利用了系統的information_schema.COLUMNS
表來獲取我們需要的一些基本信息,主要結構如下圖所示: