1、索引python
一、索引介紹mysql
通常的應用系統,讀寫比例在10:1左右,並且插入操做和通常的更新操做不多出現性能問題,在生產環境中,咱們遇到複雜的查詢操做,經過索引能夠加速查詢。程序員
索引在MySQL中也叫作「鍵」,是存儲引擎用於快速找到記錄的一種數據結構。索引對於良好的性能很是關鍵,尤爲是當表中的數據量愈來愈大時,索引對於性能的影響愈發重要。sql
索引是應用程序設計和開發的一個重要方面。若索引太多,應用程序的性能可能會受到影響。而索引太少,對查詢性能又會產生影響,要找到一個平衡點,這對應用程序的性能相當重要。數據庫
二、索引原理vim
本質:經過不斷地縮小想要獲取數據的範圍來篩選出最終想要的結果,同時把隨機的事件變成順序的事件,也就是說,有了這種索引機制,咱們能夠老是用同一種查找方式來鎖定數據。服務器
咱們把數據存儲在磁盤上,磁盤讀取數據靠的是機械運動,每次讀取數據花費的時間能夠分爲尋道時間、旋轉延遲、傳輸時間三個部分。訪問一次磁盤的時間,即一次磁盤IO的時間約等於9ms左右,因此減小磁盤IO是優化的關鍵。網絡
三、彙集索引與輔助索引數據結構
在數據庫中,B+樹的高度通常都在2~4層,這也就是說查找某一個鍵值的行記錄時最多隻須要2到4次IO,這倒不錯。由於當前通常的機械硬盤每秒至少能夠作100次IO,2~4次的IO意味着查詢時間只須要0.02~0.04秒。ide
數據庫中的B+樹索引能夠分爲彙集索引(clustered index)和輔助索引(secondary index)
彙集索引與輔助索引的對比:
相同的是:無論是彙集索引仍是輔助索引,其內部都是B+樹的形式,即高度是平衡的,葉子結點存放着全部的數據。
不一樣的是:葉子結點存放的是不是一整行的信息
(1)彙集索引
彙集索引的好處之一:它對主鍵的排序查找和範圍查找速度很是快,葉子節點的數據就是用戶所要查詢的數據。如用戶須要查找一張表,查詢最後的10位用戶信息,因爲B+樹索引是雙向鏈表,因此用戶能夠快速找到最後一個數據頁,並取出10條記錄
彙集索引的好處之二:範圍查詢(range query),即若是要查找主鍵某一範圍內的數據,經過葉子節點的上層中間節點就能夠獲得頁的範圍,以後直接讀取數據頁便可
(2)輔助索引
輔助索引的葉子節點不包含行記錄的所有數據。
葉子節點除了包含鍵值之外,每一個葉子節點中的索引行中還包含一個書籤(bookmark)。該書籤用來告訴InnoDB存儲引擎去哪裏能夠找到與索引相對應的行數據。
因爲InnoDB存儲引擎是索引組織表,所以InnoDB存儲引擎的輔助索引的書籤就是相應行數據的彙集索引鍵。
輔助索引的存在並不影響數據在彙集索引中的組織,所以每張表上能夠有多個輔助索引,但只能有一個彙集索引。
當經過輔助索引來尋找數據時,InnoDB存儲引擎會遍歷輔助索引並經過葉子級別的指針得到只想主鍵索引的主鍵,而後再經過主鍵索引來找到一個完整的行記錄。
四、總結
(1) 必定是爲搜索條件的字段建立索引,好比select * from s1 where id = 333;就須要爲id加上索引
(2) 在表中已經有大量數據的狀況下,建索引會很慢,且佔用硬盤空間,建完後查詢速度加快
(3) 須要注意的是:innodb表的索引會存放於s1.ibd文件中,而myisam表的索引則會有單獨的索引文件table1.MYI
五、使用索引
並非說咱們建立了索引就必定會加快查詢速度,若想利用索引達到預想的提升查詢速度的效果,咱們在添加索引時,必須遵循如下問題:
(1)範圍問題,或者說條件不明確,條件中出現這些符號或關鍵字:>、>=、<、<=、!= 、between...and...、like、
(2)儘可能選擇區分度高的列做爲索引,區分度的公式是count(distinct col)/count(*),表示字段不重複的比例,比例越大咱們掃描的記錄數越少,惟一鍵的區分度是1,而一些狀態、性別字段可能在大數據面前區分度就是0,使用場景不一樣,這個值也很難肯定,通常須要join的字段咱們都要求是0.1以上,即平均1條掃描10條記錄
(3)=和in能夠亂序,好比a = 1 and b = 2 and c = 3 創建(a,b,c)索引能夠任意順序,mysql的查詢優化器會幫你優化成索引能夠識別的形式
(4)索引列不能參與計算,保持列「乾淨」,好比from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,由於b+樹中存的都是數據表中的字段值,但進行檢索時,須要把全部元素都應用函數才能比較,顯然成本太大。因此語句應該寫成create_time = unix_timestamp(’2014-05-29’)
(5) and/or
# 一、and與or的邏輯
條件1 and 條件2: 全部條件都成立纔算成立,但凡要有一個條件不成立則最終結果不成立
條件1 or 條件2: 只要有一個條件成立則最終結果就成立
# 二、and的工做原理
條件:
a = 10 and b = 'xxx' and c > 3 and d = 4
索引:
製做聯合索引(d, a, b, c)
工做原理:
對於連續多個and:mysql會按照聯合索引,從左到右的順序找一個區分度高的索引字段(這樣即可以快速鎖定很小的範圍),加速查詢,即按照d— > a->b->c的順序
# 三、or的工做原理
條件:
a = 10 or b = 'xxx' or c > 3 or d = 4
索引:
製做聯合索引(d, a, b, c)
工做原理:
對於連續多個or:mysql會按照條件的順序,從左到右依次判斷,即a->b->c->d
(6)最左前綴匹配原則,對於組合索引mysql會一直向右匹配直到遇到範圍查詢(>、<、between、like)就中止匹配(指的是範圍大了,有索引速度也慢),如a = 1 and b = 2 and c > 3 and d = 4 若是創建(a,b,c,d)順序的索引,d是用不到索引的,若是創建(a,b,d,c)的索引則均可以用到,a,b,d的順序能夠任意調整。
(7)其餘
- 使用函數
select * from tb1 where reverse(email) = 'egon';
- 類型不一致
若是列是字符串類型,傳入條件是必須用引號引發來,否則...
select * from tb1 where email = 999;
# 排序條件爲索引,則select字段必須也是索引字段,不然沒法命中
- order by
select name from s1 order by email desc;
當根據索引排序時候,select查詢的字段若是不是索引,則速度仍然很慢
select email from s1 order by email desc;
特別的:若是對主鍵排序,則仍是速度很快:
select * from tb1 order by nid desc;
- 組合索引最左前綴
若是組合索引爲:(name, email)
name and email - - 命中索引
name - - 命中索引
email - - 未命中索引
- count(1)或count(列)代替count(*) 在mysql中沒有差異了
- create index xxxx on tb(title(19)) # text類型,必須制定長度
六、注意事項
- 避免使用select *
- count(1)或count(列) 代替 count(*)
- 建立表時儘可能時 char 代替 varchar
- 表的字段順序固定長度的字段優先
- 組合索引代替多個單列索引(常用多個條件查詢時)
- 儘可能使用短索引
- 使用鏈接(JOIN)來代替子查詢(Sub-Queries)
- 連表時注意條件類型需一致
- 索引散列值(重複少)不適合建索引,例:性別不適合
七、 聯合索引
聯合索引時指對錶上的多個列合起來作一個索引。聯合索引的建立方法與單個索引的建立方法同樣,不一樣之處在僅在於有多個索引列
mysql> create table t(
-> a int,
-> b int,
-> primary key(a),
-> key idx_a_b(a,b)
-> );
八、覆蓋索引
InnoDB存儲引擎支持覆蓋索引(covering index,或稱索引覆蓋),即從輔助索引中就能夠獲得查詢記錄,而不須要查詢彙集索引中的記錄。
使用覆蓋索引的一個好處是:輔助索引不包含整行記錄的全部信息,故其大小要遠小於彙集索引,所以能夠減小大量的IO操做
覆蓋索引的另一個好處是對某些統計問題而言的。
對於(a,b)形式的聯合索引,通常是不能夠選擇b中所謂的查詢條件。但若是是統計操做,而且是覆蓋索引,則優化器仍是會選擇使用該索引
2、慢查詢
一、慢查詢優化的基本步驟:
0.先運行看看是否真的很慢,注意設置SQL_NO_CACHE
1.where條件單表查,鎖定最小返回記錄表。這句話的意思是把查詢語句的where都應用到表中返回的記錄數最小的表開始查起,單表每一個字段分別查詢,看哪一個字段的區分度最高
2.explain查看執行計劃,是否與1預期一致(從鎖定記錄較少的表開始查詢)
3.order by limit 形式的sql語句讓排序的表優先查
4.瞭解業務方使用場景
5.加索引時參照建索引的幾大原則
6.觀察結果,不符合預期繼續從0分析
二、慢日誌管理
慢日誌
- 執行時間 > 10
- 未命中索引
- 日誌文件路徑
配置:
- 內存
show variables like '%query%';
show variables like '%queries%';
set global 變量名 = 值
- 配置文件
mysqld - -defaults - file = 'E:\mysql-5.7.16-winx64\mysql-5.7.16-winx64\my-default.ini'
my.conf內容:
slow_query_log = ON
slow_query_log_file = E: / ....
注意:修改配置文件以後,須要重啓服務
三、mysql日誌管理
(1)bin-log
#1. 啓用
# vim /etc/my.cnf
[mysqld]
log-bin[=dir\[filename]]
# service mysqld restart
#2. 暫停
//僅當前會話
SET SQL_LOG_BIN=0;
SET SQL_LOG_BIN=1;
#3. 查看
查看所有:
# mysqlbinlog mysql.000002
按時間:
# mysqlbinlog mysql.000002 --start-datetime="2012-12-05 10:02:56"
# mysqlbinlog mysql.000002 --stop-datetime="2012-12-05 11:02:54"
# mysqlbinlog mysql.000002 --start-datetime="2012-12-05 10:02:56" --stop-datetime="2012-12-05 11:02:54"
按字節數:
# mysqlbinlog mysql.000002 --start-position=260
# mysqlbinlog mysql.000002 --stop-position=260
# mysqlbinlog mysql.000002 --start-position=260 --stop-position=930
#4. 截斷bin-log(產生新的bin-log文件)
a. 重啓mysql服務器
b. # mysql -uroot -p123 -e 'flush logs'
#5. 刪除bin-log文件
# mysql -uroot -p123 -e 'reset master'
(2)查詢日誌
啓用通用查詢日誌
# vim /etc/my.cnf
[mysqld]
log[=dir\[filename]]
# service mysqld restart #修改配置文件後須要重啓服務
(3)慢查詢日誌
啓用慢查詢日誌
# vim /etc/my.cnf
[mysqld]
log-slow-queries[=dir\[filename]]
long_query_time=n
# service mysqld restart #修改配置文件後須要重啓服務
MySQL 5.6:
slow-query-log=1
slow-query-log-file=slow.log
long_query_time=3
查看慢查詢日誌
測試:BENCHMARK(count,expr)
SELECT BENCHMARK(50000000,2*3);
pip3 install pymysql #安裝模塊
一、建立表
import pymysql conn=pymysql.connect( host='127.0.0.1', port=3306, user='root', password='', db='test' ) cursor=conn.cursor() #拿遊標 sql=''' create table info( id int primary key auto_increment, username char(16), password char(20) ); ''' cursor.execute(sql) #提交sql conn.commit() cursor.close() #關閉(遊標) conn.close() #斷開鏈接
二、插入數據
import pymysql conn=pymysql.connect( host='127.0.0.1', port=3306, user='root', password='', db='test' ) cursor=conn.cursor() #拿遊標 # sql='insert into info(username,password) values("root","123456");' #插入單條數據 # cursor.execute(sql) sql='insert into info(username,password) values(%s,%s);' res=cursor.executemany(sql,[("root","123456"),("lhf","12356"),("eee","156")]) #插入多條記錄,執行sql語句,返回sql影響成功的行數 print (res) cursor.close() conn.commit() #數據的增、刪、改必需要提交 conn.close()
三、查詢數據操做
import pymysql conn=pymysql.connect( host='127.0.0.1', port=3306, user='root', password='', db='test' ) cursor=conn.cursor(pymysql.cursors.DictCursor) #數據以字典形式顯示 # cursor=conn.cursor() #數據以元組形式顯示 sql='select * from info' rows=cursor.execute(sql) print(cursor.fetchone()) #取出第一條數據 {'id': 1, 'username': 'root', 'password': '123456'} # print(cursor.fetchmany(3)) #能夠指定顯示數據的條數 #print(cursor.fetchall()) #顯示全部的數據 cursor.scroll(0,mode='absolute') # 相對絕對位置移動 print(cursor.fetchone()) #顯示第一條數據{'id': 1, 'username': 'root', 'password': '123456'} cursor.scroll(1,mode='relative') # 相對當前位置移動 print(cursor.fetchone()) #顯示第三條數據{'id': 3, 'username': 'eee', 'password': '156'} cursor.close() conn.close()
四、execute()之sql注入
根本原理:就根據程序的字符串拼接name='%s',咱們輸入一個xxx' -- haha,用咱們輸入的xxx加'在程序中拼接成一個判斷條件name='xxx' -- haha'
(1)以前的版本
import pymysql user=input('user>>: ').strip() pwd=input('password>>: ').strip() conn=pymysql.connect( host='127.0.0.1', port=3306, user='root', password='', db='test' ) cursor=conn.cursor(pymysql.cursors.DictCursor) sql='select id from info where username="%s" and password="%s"' %(user,pwd) print(sql) #用戶名爲:eee" -- aa ,密碼爲空,這時候sql就select * from info where username="eee" -- aa" and password="",登陸成功 #用戶名爲:xxx" or 1=1 -- aa ,密碼爲空,這時候sql就select * from info where username="xxx" or 1=1 -- aa" and password=""登陸成功 rows=cursor.execute(sql) if rows: print('登陸成功') else: print('用戶名或密碼錯誤') cursor.close() conn.commit() conn.close()
(2)修改後的版本
import pymysql user=input('user>>: ').strip() pwd=input('password>>: ').strip() conn=pymysql.connect( host='127.0.0.1', port=3306, user='root', password='', db='test' ) cursor=conn.cursor(pymysql.cursors.DictCursor) sql='select * from user where username=%s and password=%s' rows=cursor.execute(sql,(user,pwd)) if rows: print('登陸成功') else: print('用戶名或密碼錯誤') cursor.close() conn.commit() conn.close() #修改後,pymysql會處理掉用戶輸入的特殊字符,就解決了sql的注入問題
五、獲取插入的最後一條數據的自增ID
import pymysql conn=pymysql.connect(host='127.0.0.1',user='root',password='',database='test') cursor=conn.cursor() sql='insert into info(name,password) values("xxx","123");' rows=cursor.execute(sql) print(cursor.lastrowid) #在插入語句後查看 conn.commit() cursor.close() conn.close()
4、視圖
視圖是一個虛擬表,只有表結構,沒有數據
使用視圖咱們能夠把查詢過程當中的臨時表摘出來,用視圖去實現,這樣之後再想操做該臨時表的數據時就無需重寫複雜的sql了,直接去視圖中查找便可,但視圖有明顯地效率問題,而且視圖是存放在數據庫中的,若是咱們程序中使用的sql過度依賴數據庫中的視圖,即強耦合,那就意味着擴展sql極爲不便,所以並不推薦使用
咱們不該該修改視圖中的記錄,並且在涉及多個表的狀況下是根本沒法修改視圖中的記錄的。
5、觸發器
一、建立觸發器
# 插入前
CREATE TRIGGER tri_before_insert_tb1 BEFORE INSERT ON tb1 FOR EACH ROW
BEGIN
...
END
# 插入後
CREATE TRIGGER tri_after_insert_tb1 AFTER INSERT ON tb1 FOR EACH ROW
BEGIN
...
END
# 刪除前
CREATE TRIGGER tri_before_delete_tb1 BEFORE DELETE ON tb1 FOR EACH ROW
BEGIN
...
END
# 刪除後
CREATE TRIGGER tri_after_delete_tb1 AFTER DELETE ON tb1 FOR EACH ROW
BEGIN
...
END
# 更新前
CREATE TRIGGER tri_before_update_tb1 BEFORE UPDATE ON tb1 FOR EACH ROW
BEGIN
...
END
# 更新後
CREATE TRIGGER tri_after_update_tb1 AFTER UPDATE ON tb1 FOR EACH ROW
BEGIN
...
END
二、插入後觸發觸發器
#準備表 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 // #定義sql結束符 CREATE TRIGGER tri_after_insert_cmd AFTER INSERT ON cmd FOR EACH ROW BEGIN IF NEW.success = 'no' THEN #等值判斷只有一個等號 INSERT INTO errlog(err_cmd, err_time) VALUES(NEW.cmd, NEW.sub_time) ; #必須加分號 END IF ; #必須加分號 END// #sql語句結束 delimiter ; #sql結束符修改成以前的分號 #往表cmd中插入記錄,觸發觸發器,根據IF的條件決定是否插入錯誤日誌 INSERT INTO cmd ( USER, priv, cmd, sub_time, success ) VALUES ('egon','0755','ls -l /etc',NOW(),'yes'), ('egon','0755','cat /etc/passwd',NOW(),'no'), ('egon','0755','useradd xxx',NOW(),'no'), ('egon','0755','ps aux',NOW(),'yes'); #查詢錯誤日誌,發現有兩條 mysql> select * from errlog; +----+-----------------+---------------------+ | id | err_cmd | err_time | +----+-----------------+---------------------+ | 1 | cat /etc/passwd | 2017-09-14 22:18:48 | | 2 | useradd xxx | 2017-09-14 22:18:48 | +----+-----------------+---------------------+
特別的:NEW表示即將插入的數據行,OLD表示即將刪除的數據行。
#觸發器沒法由用戶直接調用,而是因爲對錶的【增/刪/改】操做被動引起的
6、事務
事務用於將某些操做的多個SQL做爲原子性操做,一旦有某一個出現錯誤,便可回滾到原來的狀態,從而保證數據庫數據完整性。
create table user( id int primary key auto_increment, name char(32), balance int ); insert into user(name,balance) values ('wsb',1000), ('egon',1000), ('ysb',1000); #原子操做 start transaction; update user set balance=900 where name='wsb'; #買支付100元 update user set balance=1010 where name='egon'; #中介拿走10元 update user set balance=1090 where name='ysb'; #賣家拿到90元 commit; #出現異常,回滾到初始狀態 好比:賣家應該拿到90元,出現異常而沒有拿到90元,須要回到以前的數據 rollback; #回滾操做,在數據提交前有效,提交後沒法回滾 commit; #提交數據 mysql> select * from user; +----+------+---------+ | id | name | balance | +----+------+---------+ | 1 | wsb | 1000 | | 2 | egon | 1000 | | 3 | ysb | 1000 | +----+------+---------+
7、存儲過程
一、存儲過程介紹
存儲過程包含了一系列可執行的sql語句,存儲過程存放於MySQL中,經過調用它的名字能夠執行其內部的一堆sql
使用存儲過程的優勢:
#1. 用於替代程序寫的SQL語句,實現程序與sql解耦
#2. 基於網絡傳輸,傳別名的數據量小,而直接傳sql數據量大
使用存儲過程的缺點:
#1. 程序員擴展功能不方便
程序與數據庫結合使用的三種方式
#方式一:
MySQL:存儲過程
程序:調用存儲過程
#方式二:
MySQL:
程序:純SQL語句
#方式三:
MySQL:
程序:類和對象,即ORM(本質仍是純SQL語句)
二、建立無參的存儲過程
delimiter // create procedure p1() BEGIN select * from blog; INSERT into blog(name,sub_time) values("xxx",now()); END // delimiter ; #在mysql中調用 call p1() #在python中基於pymysql調用 cursor.callproc('p1') print(cursor.fetchall())
三、建立有參的存儲過程
對於存儲過程,能夠接收參數,其參數有三類:
(1)in #僅用於傳入參數用
delimiter // create procedure p2( in n1 int, in n2 int ) BEGIN select * from blog where id > n1; END // delimiter; # 在mysql中調用 call p2(3, 2) # 在python中基於pymysql調用 cursor.callproc('p2', (3, 2)) print(cursor.fetchall())
(2)out #僅用於返回值用
delimiter // create procedure p3( in n1 int, out res int ) BEGIN select * from blog where id > n1; set res = 1; END // delimiter ; #在mysql中調用 set @res=0; #0表明假(執行失敗),1表明真(執行成功) call p3(3,@res); select @res; #在python中基於pymysql調用 cursor.callproc('p3',(3,0)) #0至關於set @res=0 print(cursor.fetchall()) #查詢select的查詢結果 cursor.execute('select @_p3_0,@_p3_1;') #@p3_0表明第一個參數,@p3_1表明第二個參數,即返回值 print(cursor.fetchall())
(3)inout #既能夠傳入又能夠看成返回值
delimiter // create procedure p4( inout n1 int ) BEGIN select * from blog where id > n1; set n1 = 1; END // delimiter ; #在mysql中調用 set @x=3; call p4(@x); select @x; #在python中基於pymysql調用 cursor.callproc('p4',(3,)) print(cursor.fetchall()) #查詢select的查詢結果 cursor.execute('select @_p4_0;') print(cursor.fetchall())
四、事務的應用
delimiter // create PROCEDURE p5(OUT p_return_code tinyint) BEGIN DECLARE exit handler for sqlexception BEGIN -- ERROR set p_return_code = 1; rollback; END; DECLARE exit handler for sqlwarning BEGIN -- WARNING set p_return_code = 2; rollback; END; START TRANSACTION; DELETE from tb1; #執行失敗 insert into blog(name,sub_time) values('yyy',now()); COMMIT; -- SUCCESS set p_return_code = 0; #0表明執行成功 END // delimiter ; #在mysql中調用存儲過程 set @res=123; call p5(@res); select @res; #在python中基於pymysql調用存儲過程 cursor.callproc('p5',(123,)) print(cursor.fetchall()) #查詢select的查詢結果 cursor.execute('select @_p5_0;') print(cursor.fetchall())
五、執行存儲過程
#!/usr/bin/env python # -*- coding:utf-8 -*- import pymysql conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123', db='t1') cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) # 執行存儲過程 cursor.callproc('p1', args=(1, 22, 3, 4)) # 獲取執行完存儲的參數 cursor.execute("select @_p1_0,@_p1_1,@_p1_2,@_p1_3") result = cursor.fetchall() conn.commit() cursor.close() conn.close() print(result)
六、函數
date_format函數
(1)基本使用
mysql> SELECT DATE_FORMAT('2009-10-04 22:23:00', '%W %M %Y');
-> 'Sunday October 2009'
mysql> SELECT DATE_FORMAT('2007-10-04 22:23:00', '%H:%i:%s');
-> '22:23:00'
mysql> SELECT DATE_FORMAT('1900-10-04 22:23:00',
-> '%D %y %a %d %m %b %j');
-> '4th 00 Thu 04 10 Oct 277'
mysql> SELECT DATE_FORMAT('1997-10-04 22:23:00',
-> '%H %k %I %r %T %S %w');
-> '22 22 10 10:23:00 PM 22:23:00 00 6'
mysql> SELECT DATE_FORMAT('1999-01-01', '%X %V');
-> '1998 52'
mysql> SELECT DATE_FORMAT('2006-06-00', '%d');
-> '00'
(2) 準備表和記錄
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');
(3) 提取sub_time字段的值,按照格式後的結果即"年月"來分組
SELECT DATE_FORMAT(sub_time,'%Y-%m'),COUNT(1) FROM blog GROUP BY DATE_FORMAT(sub_time,'%Y-%m'); #結果 +-------------------------------+----------+ | DATE_FORMAT(sub_time,'%Y-%m') | COUNT(1) | +-------------------------------+----------+ | 2015-03 | 2 | | 2016-07 | 4 | | 2017-03 | 3 | +-------------------------------+----------+