MYSQL報錯語句很多,但是了解其原理才是做重要的
讓我們先看一段報錯語句
select count(*),(floor(rand(0)*2))x from information_schema.tables group by x
複制
這條報錯的語句最重要的部分有三個:
- rand(0)
- floor()
- group by
現在我就講一下這三個部分
一、rand(0)
rand()是用來産生随機數的,他的範圍是[0,1]之間
可以看到每一次的數值都是不同的
而且rand()有一個BUG,報錯也就是利用了這個BUG,這個後面會細說,暫時就了解産生随機數就好了
二、floor()
floor()是用來取整的,重點:沒有四舍五入
這樣一來用floor()來包裹着rand()豈不是沒有用了嗎?因為結果一定會是0。
是以我們要對rand()進行處理,那就是對他乘個2,這樣就會出現1。
這樣子我們會有個疑問,因為是rand()是随機的是以出現0,1也是随機的,是不可控的,可以看到确實是随機的
是以我們要對rand()做一點點修改,将rand()加個0,變成rand(0)
加一個0以後的我們發現數字的結果就唯一,是01101,這串數字特别的重要,我們的報錯就是來自于他重要的事情說三遍:01101,01101,01101
三、group by
為了示範友善我建立了一個test表
接下來我們用一下group by 看看有什麼作用
select * from test group by age;
複制
可以看到 group by 建立了一個新的虛空的表,并且以 by 後的字段 age 來查詢test表,如果重複了就不再添加,而且新的虛拟表的主鍵是 by 後面的字段,這就是上圖中的 age 。(ps:一個表中主鍵是不能重複的)
現在就要進入重點了,在此之前我再介紹一下count()
count()函數允許對表中符合特定條件數的所有行進行計數,舉個例子
select count(*) from test group by age;
複制
用count(),可以清楚的知道表中,幾個15歲,幾個18歲,幾個19和20歲。
OK,現在我們開始
我們來看這個語句
select count(*) from test group by floor(rand(0)*2);
複制
會注意到是不是和文章開始的報錯語句不太一樣,其實文章開始的那個報錯語句是這個的變形而已,為了更直覺咱們就看這個。
還記得我之前說的 rand() 的一個小BUG嗎,那就是
就是查詢的時候如果使用rand()的話,該值會被計算多次,這個是MySql官方說的,這個“多次計算”在咱的報錯語句中來解讀就是, group by floor(rand(0)*2)
在執行是會計算一次 floor(rand(0)*2)
,但是在插入資料時還會執行一次 floor(rand(0)*2)
。
group by floor(rand(0)*2)
floor(rand(0)*2)
floor(rand(0)*2)
還有一個,我在提醒一次 floor(rand(0)*2)
的前5個計算結果為 01101
floor(rand(0)*2)
語句開始運作
- group by 以
floor(rand(0)*2)
為主鍵,建立一個虛拟的空表
keycount
- 查詢第一條記錄,計算
(這是第一次計算),得到的值是0,檢視表發現沒有0,是以進行插入操作,但是在插入時又會計算一次floor(rand(0)*2)
floor(rand(0)*2)
(這是第二次計算)結果為1,此時的表為
keycount 11
- 查詢第二條記錄,計算
floor(rand(0)*2)
(這是第三次計算),得到值是1,檢視表發現已經有了,則不進行插入操作,直接count+1,此時表為
keycount 12
- 查詢滴三條記錄,計算
(這是第四次計算),得到的是0,檢視表發現沒有,進行插入操作,但是在插入之前會在此計算floor(rand(0)*2)
floor(rand(0)*2)
(這是第五次計算),得到的是1。然而表中已經有key為1 ,是以會産生報錯。
keycount 12 1 (出錯)
總結
出現報錯的原因是,因為已經要執行插入資料操作,才發現了主鍵沖突。
這個報錯是利用 rand() 的特殊性,以及
floor(rand(0)*2)
前5個數字的可預知性,以及可以通過 group by 來建立一個空的虛拟表,這些條件綜合在一起産生的報錯。
Q.E.D.