登陸MySql命令: mysql -u用戶名 -p用戶密碼
增刪改查(表/數據) 基本操作
創建表
(1、字段不爲null(深受設計人員的迫害) 2、字段有默認值 3、時間類型用時間戳 4、默認 innodb 支持事務)
CREATE TABLE emplsdsoyee_plan(
id INT ( 11 ) PRIMARY KEY auto_increment COMMENT '編號',
emplid VARCHAR ( 11 ) NOT NULL DEFAULT '' COMMENT '工號',
name VARCHAR ( 11 ) NOT NULL DEFAULT '' COMMENT '名字',
passcode VARCHAR ( 32 ) NOT NULL DEFAULT '' COMMENT '身份證',
relation INT ( 11 ) NOT NULL DEFAULT 3 COMMENT '關係',
prodid INT ( 11 ) NOT NULL DEFAULT 0 COMMENT '計劃',
planname VARCHAR ( 20 ) NOT NULL DEFAULT '' COMMENT '計劃名稱',
isdeleted INT ( 11 ) NOT NULL DEFAULT 0 COMMENT '是否有效 (0有效 1失效)',
createuserid VARCHAR ( 20 ) NOT NULL DEFAULT '' COMMENT '創建者',
updateuserid VARCHAR ( 20 ) NOT NULL DEFAULT '' COMMENT '修改者',
createtime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
updatetime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間'
) auto_increment = 1 ENGINE = INNODB DEFAULT CHARSET = utf8
要符合 三範式
1、字段具有原子性,不可再分
2、要有主關鍵字,方便關聯
3、一張表中不包含其他表中所包含的非主關鍵字
----------------------------------------- 很 基 礎 的 新 建 表 ---------------------------------------------------------
修改,新增,刪除 表 字段
添加表的字段 ALTER TABLE 表名 ADD 字段名 字段的類型
-- 後面可以接 AFTER 或者 BEFORE 字段名
例 : alter table employee_plan add test1 varchar(50) after relation;
修改表的字段名 ALTER TABLE 表名 CHANGE 舊字段名 新字段名 新數據類型
例 : alter table employee_plan change test1 test2 decimal(10,1);
修改表的字段類型 ALTER TABLE 表名 MODIFY 字段名 新數據類型
例 : alter table employee_plan modify test2 int(30);
刪除表的字段 ALTER TABLE 表名 DROP 字段名
例 : alter table employee_plan drop test2;
加索引
1.PRIMARY KEY(主鍵索引)
mysql>ALTER TABLE 表名 ADD PRIMARY KEY ( `column` )
2.UNIQUE(唯一索引)
mysql>ALTER TABLE 表名 ADD UNIQUE (`column` )
3.INDEX(普通索引)
mysql>ALTER TABLE 表名 ADD INDEX `索引別名` ( `column` )
4.FULLTEXT(全文索引)
mysql>ALTER TABLE 表名 ADD FULLTEXT ( `column` )
5.多列索引
mysql>ALTER TABLE 表名 ADD INDEX index_name ( `column1`, `column2`, `column3` )
增 刪 改 查 SQL
------ 新增語句
INSERT INTO 表名(表中的字段,,)value(字段所對應的記錄,,);
表1 和 表2 字段一致的話
INSERT INTO 表1 SELECT * FROM 表2 where 後面可以接條件
表1 和 表2 字段不一致的話
INSERT INTO 表1(表中的字段,,)SELECT(表中的字段,,)FROM 表2 where 後面可以接條件
------ 更新語句
UPDATE 表名 SET 要修改的值 where 修改條件
例 : UPDATE bx_employeeinfo set `name` ='測試' where emplid='0176201'
ps: mysql 是有關鍵字的 name 爲關鍵字 用 `` 可以表示 非關鍵字
多表更新 UPDATE 表1,表2 SET 表1.name = 表2.name WHERE 表1.emplid = 表2.emplid AND 其它條件
----- 刪除語句
DELETE FROM 表名 where 刪除條件
例 : delete from bx_employeeinfo_job_temp where id = 781736
多表刪除 DELETE 表1,表2 FROM 表1,表2 WHERE 表1.emplid = 表2.emplid AND 其它條件
(刪除表1,表2 符合條件的數據,如果只有 delete 表1 from ... 那麼只刪除表1)
ps: 建議使用 left join ,主表放在最左;
--- 查詢 語句 都很熟悉了。。。 略。。。
SELECT * from 表名 where 查詢條件
查詢表字段名、註釋、字段類型
select COLUMN_NAME,DATA_TYPE,COLUMN_COMMENT from information_schema.COLUMNS
where table_name = '表名' and table_schema = '數據庫名稱';
修改字段字符集(utf8->utf8mb4)
SELECT
CONCAT(
'ALTER TABLE ',
TABLE_NAME,
' CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;'
) AS target_tables
FROM
INFORMATION_SCHEMA.TABLES
WHERE
TABLE_SCHEMA = '數據庫名稱'
AND TABLE_TYPE = 'BASE TABLE';
得到很多條命令執行即可
字段約束種類
約束類型: | 主鍵 | 外鍵 | 唯一 | 非空 | 自增 | 默認值 |
---|---|---|---|---|---|---|
關鍵字: | primary key | foreign key |
unique |
not null |
auto_increment |
default |
truncate、delete drop 區別
DELETE 支持事務 後面2者不支持
DROP用於刪除表,數據庫等 RUNCATE和DELETE都是刪除表數據
DELETE 清空數據,不會刪除表
TRUNCATE 刪除表 再創建一張空的表
速度上 一般來說: drop> truncate > delete
自增(主鍵)問題
有時候 查詢 max(主鍵字段) 並不是真正的 自增的‘’起點‘’
---- 查詢 表裏面 包含 auto_increment 自增約束的 狀態 可以查詢到 自增 值
SHOW TABLE STATUS WHERE NAME= 'bx_employeeinfo_family_history'
-- 修改 自增值
ALTER TABLE bx_employeeinfo_family_history auto_increment='設置的自增起點'
count(1)、 count(*) 、count(列名)區別
執行效果上:
count(*)包括了所有的列,相當於行數,在統計結果的時候,不會忽略列值爲NULL
count(1)包括了忽略所有列,用1代表代碼行,在統計結果的時候,不會忽略列值爲NULL
count(列名)只包括列名那一列,在統計結果的時候,會忽略列值爲空(這裏的空不是隻空字符串或者0,而是表示null)的計數,即某個字段值爲NULL時,不統計。
執行效率上:
列名爲主鍵,count(列名)會比count(1)快
列名不爲主鍵,count(1)會比count(列名)快
如果表多個列並且沒有主鍵,則 count(1) 的執行效率優於 count(*)
如果有主鍵,則 select count(主鍵)的執行效率是最優的
如果表只有一個字段,則 select count(*)最優。
int(10) int(20)一樣嗎
實際上沒有任何區別的,雖然制定的長度不一樣,但是 系統還是按照
int(-2147483648 - 2147483647)的長度來算的
----------------------------------------------------------------------------------------------------------------------
日期 時間 相關操作
~獲取 當前時間 年月日 ,時分秒
SELECT CURRENT_DATE(),CURRENT_TIME()
~獲取最新時間,是同義詞 通用的
SELECT CURRENT_TIMESTAMP() 或者 SELECT CURRENT_TIMESTAMP
SELECT now();
1-1 日期格式化
DATE_FORMAT(‘2019-01-11 09:08:07’, '%Y-%m-%d')
注意事項:分鐘 參數爲 i
SELECT DATE_FORMAT(CURRENT_TIMESTAMP() , '%Y-%m-%d %h:%i:%s') as 時間
1-2 日期的新增 和 減少
語法 :DATE_ADD(date,INTERVAL expr type)
expr : 修改的值(變量)
type: 類型
SECOND 秒
MINUTE 分
HOUR 時
DAY 天
WEEK 周
MONTH 月
QUARTER 季
YEAR 年
SELECT now() as 當前時間, DATE_ADD(now(),INTERVAL 1 YEAR) 新增一年
常用的函數(不定時更新)
1、LENGTH(字段名) - - 查詢字段的長度(字符的長度) utf-8模式下 英文&&數字 爲1個字節,中文的爲3個字節,
2、CHAR_LENGTH(字段名) - - 以字符數量來獲取長度,與中英文無關。
3、CASE WHEN... THEN.... ELSE.... END函數 也就是 if else;
使用詳情如下
SELECT e.emplid,'計劃調整',e.name,e.passcode,h.`name`,
(case when h.relation=0 then '01'
when h.relation=1 then '03'
when h.relation=2 and h.sex=0 then '04'
when h.relation=3 then '00'
ELSE '05' END
)as 關係,
(case h.sex when 0 then '男' else '女' end)as 性別,
h.birthday,
h.identifytype,
h.passcode,
p.prodname
FROM bx_employeeinfo_family_history h
INNER JOIN ?bx_employeeinfo e ON e.emplid = h.emplid
INNER JOIN bx_product p ON h.prodid= p.prodid
WHERE
h.effstartdate='2018-09-01' and h.effenddate='2019-08-31' and h.emplid='0176201'
輸出結果:
4、 CONCAT(字段名1,字段名2) 與 CONCAT_WS('分隔符',字段名1,字段名2) - - 字段的拼接 ps: 分隔符 必須加' ' 引號
例如:SELECT CONCAT_WS('我是分隔符','abc','iop') as 測試拼接
結果:
5、SUBSTR(str, start, length) 與 SUBSTRING(str, start, length) - - 從字符串 str中的第 start 個位置截取 length個 字符(start從1開始算的)
LEFT(str,n)與 RIGHT(str,n) - - 返回字符串 str 的(前/後) n 個字符
例如:SELECT CONCAT_WS('-',left('203040',2),SUBSTR('203040',3,2),right('203040',2)) as 拼接
結果:
6、REPLACE(str,'2','3'); - - 將字符串str 中的 2 替換爲3
UPDATE student set age = REPLACE(age,'2','3');
7、IF(str = null,'2',str); - - 如果str爲null,則使用'2'
mysql> select name,if(sex=0,'女','男') as sex from student;
+-------+-----+
| name | sex |
+-------+-----+
| name1 | 女 |
| name2 | 女 |
| name3 | 男 |
| name4 | 女 |
+-------+-----+
4 rows in set (0.00 sec)
索引 想要優化sql 索引是不可避免的
索引的類型
分爲: 聚集索引(也就是說主鍵,存的是數據),非聚集索引(非主鍵,存的是地址)
-
聚集索引:一張表只有一個聚集索引,數據庫會自動爲添加了主鍵的表創建聚集索引,一般情況下聚集索引就是通過主鍵值創建的索引,聚集索引的樹狀結構中保存了數據
-
非聚集索引:一張表可以有多個非聚集索引,通過非主鍵字段創建的索引稱爲非聚集索引,非聚集索引中沒有數據保存的是磁盤塊的地址。
非聚集索引 又包括:普通索引,唯一索引,聚合(複合)索引,全文索引;
索引的兩種常用的方式
BTree索引 :B+樹索引具有範圍查找和前綴查找的能力,對於有N節點的B樹,檢索一條記錄的複雜度爲O(LogN)
相當於二分查找。
Hash索引 : 哈希索引只能做等於查找,但是無論多大的Hash表,查找複雜度都是O(1) 推薦使用
顯然,如果值的差異性大,並且以等值查找(=、 <、>、in)爲主,Hash索引是更高效的選擇,它有O(1)的查找複雜度。
如果值的差異性相對較差,並且以範圍查找爲主,B樹是更好的選擇,它支持範圍查找。
MySQL的BTree索引使用的是B樹中的B+Tree,但對於主要的兩種存儲引擎的實現方式是不同的。
-
MyISAM: B+Tree葉節點的data域存放的是數據記錄的地址。在索引檢索的時候,首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,則取出其 data 域的值,然後以 data 域的值爲地址讀取相應的數據記錄。這被稱爲“非聚簇索引”。
-
InnoDB: 其數據文件本身就是索引文件。相比MyISAM,索引文件和數據文件是分離的,其表數據文件本身就是按B+Tree組織的一個索引結構,樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,因此InnoDB表數據文件本身就是主索引。這被稱爲“聚簇索引(或聚集索引)”。而其餘的索引都作爲輔助索引,輔助索引的data域存儲相應記錄主鍵的值而不是地址,這也是和MyISAM不同的地方。在根據主索引搜索時,直接找到key所在的節點即可取出數據;在根據輔助索引查找時,則需要先取出主鍵的值,再走一遍主索引。 因此,在設計表的時候,不建議使用過長的字段作爲主鍵,也不建議使用非單調的字段作爲主鍵,這樣會造成主索引頻繁分裂。
如何創建索引(常用)
- 普通索引:create index 索引名 on 表名(字段名(長度));
- 複合索引:create index 索引名 on 表名(字段名(長度),字段名(長度));
- 唯一索引:create unique index 索引名 on 表名(字段名(長度));
-
創建title索引
create index index item2title on item2(title); // 普通索引
-
create index index item2title_price on item2(title,price); //複合索引
查看索引
show index from item2;
刪除索引
drop index index_item2_title on item2;
什麼情況下不會走索引
0.關聯查詢的時候 關聯字段的雖然都加了索引,如果兩張表該字段的 字符集或字段長度不一樣 不會走索引(Mysql 是這樣的)
1.如果條件中有or,即使其中有條件帶索引也不會使用(這也是爲什麼儘量少用or的原因) 要想使用or,又想讓索引生效,只能將or條件中的每個列都加上索引
2.對於多列索引,不是使用的第一部分,則不會使用索引(複合索引的最左原則,下面會有解釋的)
3.like查詢以%開頭(如果是 name LIKE '123456' 則會走索引)
4.負向查詢(not , not in, not like, <>, != ,!>,!< ) 不會使用索引 ,<,<=,=,>,>=,BETWEEN,IN 會使用使用索引 ;
5.如果列類型是字符串,那一定要在條件中將數據使用引號引用起來,否則不使用索引
6.如果mysql估計使用全表掃描要比使用索引快,則不使用索引
7. null 會導致索引形同虛設,所以在設計表結構時應避免NULL 的存在
8.mysql 不支持函數轉換,所以字段前面不能加函數,否則這將用不到索引
Mysql (聯合)複合索引最左原則
--建表
create table test3(
id int primary key auto_increment,
c1 char(10),
c2 char(10),
c3 char(10),
c4 char(10),
c5 char(10)
);
--插入數據
insert into test3 values(null,'a1','a2','a3','a4','a5');
insert into test3 values(null,'b1','b2','b3','b4','b5');
insert into test3 values(null,'c1','c2','c3','c4','c5');
insert into test3 values(null,'d1','d2','d3','d4','d5');
insert into test3 values(null,'e1','e2','e3','e4','e5');
--查看數據
select * from test3;
-- 創建索引
create index ind_test3_c1234 on test3(c1,c2,c3,c4);
show index from test3;
explain select * from test3 where c1='a1';
explain select * from test3 where c1='a1' and c2='a2';
explain select * from test3 where c1='a1' and c2='a2' and c3='a3';
explain select * from test3 where c1='a1' and c2='a2' and c3='a3' and c4='a4';
explain select * from test3 where c1='a1' and c2='a2' and c3='a3' and c4='a4' and c5='a5';
--上面五條所以肯定都有用,而且越到下面越精準
-- 請執行如下問題SQL,分析會出現的問題
--(1)
explain select * from test3 where c1='a1' and c2='a2' and c4='a4' and c3='a3' ;
--四個字段索引都有效
-- (2)
explain select * from test3 where c4='a1' and c3='a2' and c2='a4' and c1='a3' ;
--四個字段索引都有效
-- (3)
explain select * from test3 where c1='a1' and c2='a2' and c3>'a3' and c4='a4';
--c1、c2、c3索引有效,
-- (4)
explain select * from test3 where c1='a1' and c2='a2' and c4>'a4' and c3='a3';
--四個字段索引都有效
-- (5)
explain select * from test3 where c1='a1' and c2='a2' and c4='a4' order by c3;
--c1、c2有效,c3僅對排序有效
-- (6)
explain select * from test3 where c1='a1' and c2='a2' order by c3;
--c1、c2有效,c3僅對排序有效
-- (7)
explain select * from test3 where c1='a1' and c2='a2' order by c4;
--c1、c2有效,因爲中間缺少c3,所以索引排序無效。會報:Using filesort
-- (8)
explain select * from test3 where c1='a1' and c5='a5' order by c2,c3;
--c1有效,但是c2,c3用於排序,無filesort
--(9)
explain select * from test3 where c1='a1' and c5='a5' order by c3,c2;
-- 只用了c1這個字段索引,但是由於c3,c2順序顛倒了,所以無法使用索引排序,出現filesort
--(10)
explain select * from test3 where c1='a1' and c2='a2' and c5='a5' order by c3,c2;
--因爲排序字段c2已經是一個常量了,所以對於後面c2排序其實沒有啥意義,所以不會出現filesort
--(11)
explain select * from test3 where c1='a1' and c4='c4' group by c2,c3;
--用到了c1字段索引,但用到c2、c3排序
--(12)
explain select * from test3 where c1='a1' and c4='c4' group by c3,c2;
-- 索引字段順序不正確,出現了Using temporary; Using filesort
in和exists區別
優化規則: 小表驅動大表,即小的數據集驅動大的數據集
-- 當A的數據集大於B的數據集時, in 優於 exists
select * from A where id in (select id from B);
-- 當A的數據集小於B的數據集時, exists優於in
select * from A where exists(select 1 from B where B.id = A.id);
not in 和not exists
如果查詢語句使用了not in 那麼內外表都進行全表掃描,沒有用到索引;而not extsts 的子查詢依然能用到表上的索引。所以無論那個表大,用not exists都比not in要快。
MyISAM和InnoDB區別
MyISAM不支持事務和行級鎖,而且最大的缺陷就是崩潰後無法安全恢復。不過,5.5版本之後,MySQL引入了InnoDB(事務性數據庫引擎),MySQL 5.5版本後默認的存儲引擎爲InnoDB。
大多數時候我們使用的都是 InnoDB 存儲引擎,但是在某些情況下使用 MyISAM 也是合適的比如讀密集的情況下。(如果你不介意 MyISAM 崩潰回覆問題的話)。
兩者的對比:
-
是否支持行級鎖 : MyISAM 只有表級鎖(table-level locking),而InnoDB 支持行級鎖(row-level locking)和表級鎖,默認爲行級鎖。
-
是否支持事務和崩潰後的安全恢復: MyISAM 強調的是性能,每次查詢具有原子性,其執行數度比InnoDB類型更快,但是不提供事務支持。但是InnoDB 提供事務支持事務,外部鍵等高級數據庫功能。 具有事務(commit)、回滾(rollback)和崩潰修復能力(crash recovery capabilities)的事務安全(transaction-safe (ACID compliant))型表。
-
是否支持外鍵: MyISAM不支持,而InnoDB支持。
-
是否支持MVCC :僅 InnoDB 支持。應對高併發事務, MVCC比單純的加鎖更高效;MVCC只在
READ COMMITTED
和REPEATABLE READ
兩個隔離級別下工作;MVCC可以使用 樂觀(optimistic)鎖 和 悲觀(pessimistic)鎖來實現;各數據庫中MVCC實現並不統一。
《MySQL高性能》上面有一句話這樣寫到:
不要輕易相信“MyISAM比InnoDB快”之類的經驗之談,這個結論往往不是絕對的。在很多我們已知場景中,InnoDB的速度都可以讓MyISAM望塵莫及,尤其是用到了聚簇索引,或者需要訪問的數據都可以放入內存的應用。
一般情況下我們選擇 InnoDB 都是沒有問題的,但是某事情況下你並不在乎可擴展能力和併發能力,也不需要事務支持,也不在乎崩潰後的安全恢復問題的話,選擇MyISAM也是一個不錯的選擇。但是一般情況下,我們都是需要考慮到這些問題的。
事務的 ACID
- Atomicity:原子性,最小不可拆分 全部成功或全部失敗
- Consistency:一致性,從一個一致狀態到另一個一致狀態
- Isolation:隔離性,多個事務之間互不影響
- Durability:持久性,事務完成後數據提交到數據庫中持久保存
事務相關指令
-
查看自動提交狀態
show variables like '%autocommit%';
-
設置自動提交狀態
set autocommit=0/1 0- 不自動提交 1-自動提交;
-
提交
commit;
-
回滾
rollback;
-
保存回滾點
savepoint s1;
-
回滾到某個回滾點
rollback to s1;
併發事務帶來的問題
在典型的應用程序中,多個事務併發運行,經常會操作相同的數據來完成各自的任務(多個用戶對統一數據進行操作)。併發雖然是必須的,但可能會導致以下的問題。
-
髒讀(Dirty read): 當一個事務正在訪問數據並且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時另外一個事務也訪問了這個數據,然後使用了這個數據。因爲這個數據是還沒有提交的數據,那麼另外一個事務讀到的這個數據是“髒數據”,依據“髒數據”所做的操作可能是不正確的。
-
不可重複讀(Unrepeatableread): 指在一個事務內多次讀同一數據。在這個事務還沒有結束時,另一個事務也訪問該數據。那麼,在第一個事務中的兩次讀數據之間,由於第二個事務的修改導致第一個事務兩次讀取的數據可能不太一樣。這就發生了在一個事務內兩次讀到的數據是不一樣的情況,因此稱爲不可重複讀。
-
幻讀(Phantom read): 幻讀與不可重複讀類似。它發生在一個事務(T1)讀取了幾行數據,接着另一個併發事務(T2)插入了一些數據時。在隨後的查詢中,第一個事務(T1)就會發現多了一些原本不存在的記錄,就好像發生了幻覺一樣,所以稱爲幻讀。
不可重複度和幻讀區別:
不可重複讀的重點是修改比如多次讀取一條記錄發現其中某些列的值被修改,幻讀的重點在於新增 比如多次讀取一條記錄發現記錄增多了。
事務隔離級別有哪些?MySQL的默認隔離級別是?
SQL 標準定義了四個隔離級別:
-
READ-UNCOMMITTED(讀取未提交): 最低的隔離級別,允許讀取尚未提交的數據變更,可能會導致髒讀、幻讀或不可重複讀。
-
READ-COMMITTED(讀取已提交): 允許讀取併發事務已經提交的數據,可以阻止髒讀,但是幻讀或不可重複讀仍有可能發生。
-
REPEATABLE-READ(可重複讀): 對同一字段的多次讀取結果都是一致的,除非數據是被本身事務自己所修改,可以阻止髒讀和不可重複讀,但幻讀仍有可能發生。
-
SERIALIZABLE(可串行化): 最高的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。
隔離級別 | 髒讀 | 不可重複讀 | 幻影讀 |
---|---|---|---|
READ-UNCOMMITTED | √ | √ | √ |
READ-COMMITTED | × | √ | √ |
REPEATABLE-READ | × | × | √ |
SERIALIZABLE | × | × | × |
MySQL InnoDB 存儲引擎的默認支持的隔離級別是 REPEATABLE-READ(可重讀)。我們可以通過SELECT @@tx_isolation;
命令來查看
mysql> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
這裏需要注意的是:與 SQL 標準不同的地方在於 InnoDB 存儲引擎在 REPEATABLE-READ(可重讀)事務隔離級別下使用的是Next-Key Lock 鎖算法,因此可以避免幻讀的產生,這與其他數據庫系統(如 SQL Server)是不同的。所以說InnoDB 存儲引擎的默認支持的隔離級別是 REPEATABLE-READ(可重讀) 已經可以完全保證事務的隔離性要求,即達到了 SQL標準的SERIALIZABLE(可串行化)隔離級別。
因爲隔離級別越低,事務請求的鎖越少,所以大部分數據庫系統的隔離級別都是READ-COMMITTED(讀取提交內容):,但是你要知道的是InnoDB 存儲引擎默認使用 REPEATABLE-READ(可重讀)並不會有任何性能損失。
InnoDB 存儲引擎在 分佈式事務 的情況下一般會用到SERIALIZABLE(可串行化)隔離級別。
幻讀問題
高性能MySQL:
所謂幻讀,指的是當某個事務在讀取某個範圍內的記錄時,另外一個事務又在該範圍內插入了新的記錄,當之前的事務再次讀取該範圍的記錄時,會產生幻行。InnoDB存儲引擎通過多版本併發控制(MVCC)解決了幻讀的問題。
用大白話解釋一下,就是事務1查詢id<10的記錄時,返回了2條記錄,接着事務2插入了一條id爲3的記錄,並提交。接着事務1查詢id<10的記錄時,返回了3條記錄,說好的可重複讀呢?結果卻多了一條數據。
MySQL通過MVCC解決了這種情況下的幻讀
還有一種情況
select 某記錄是否存在,不存在,準備插入此記錄,但執行 insert 時發現此記錄已存在,無法插入,這個就有問題了。
很多人容易搞混不可重複讀和幻讀,確實這兩者有些相似。但不可重複讀重點在於update和delete,而幻讀的重點在於insert。
注意:不可重複讀和幻讀的區別是:前者是指讀到了已經提交的事務的更改數據(修改或刪除),後者是指讀到了其他已經提交事務的新增數據。
對於這兩種問題解決採用不同的辦法,防止讀到更改數據,只需對操作的數據添加行級鎖,防止操作中的數據發生變化;而防止讀到新增數據,往往需要添加表級鎖,將整張表鎖定,防止新增數據(oracle採用多版本數據的方式實現)。
當隔離級別設置爲可串行化,強制事務串行執行,避免了前面說的幻讀的問題。
鎖機制與InnoDB鎖算法
MyISAM和InnoDB存儲引擎使用的鎖:
-
MyISAM採用表級鎖(table-level locking)。
-
InnoDB支持行級鎖(row-level locking)和表級鎖,默認爲行級鎖
表級鎖和行級鎖對比:
-
表級鎖: MySQL中鎖定 粒度最大 的一種鎖,對當前操作的整張表加鎖,實現簡單,資源消耗也比較少,加鎖快,不會出現死鎖。其鎖定粒度最大,觸發鎖衝突的概率最高,併發度最低,MyISAM和 InnoDB引擎都支持表級鎖。
-
行級鎖: MySQL中鎖定 粒度最小 的一種鎖,只針對當前操作的行進行加鎖。 行級鎖能大大減少數據庫操作的衝突。其加鎖粒度最小,併發度高,但加鎖的開銷也最大,加鎖慢,會出現死鎖。
InnoDB存儲引擎的鎖的算法有三種:
-
Record lock:單個行記錄上的鎖
-
Gap lock:間隙鎖,鎖定一個範圍,不包括記錄本身
-
Next-key lock:record+gap 鎖定一個範圍,包含記錄本身
相關知識點:
-
innodb對於行的查詢使用next-key lock
-
Next-locking keying爲了解決Phantom Problem幻讀問題
-
當查詢的索引含有唯一屬性時,將next-key lock降級爲record key
-
Gap鎖設計的目的是爲了阻止多個事務將記錄插入到同一範圍內,而這會導致幻讀問題的產生
-
有兩種方式顯式關閉gap鎖:(除了外鍵約束和唯一性檢查外,其餘情況僅使用record lock) A. 將事務隔離級別設置爲RC B. 將參數innodb_locks_unsafe_for_binlog設置爲1
大表優化
當MySQL單表記錄數過大時,數據庫的CRUD性能會明顯下降,一些常見的優化措施如下:
1. 限定數據的範圍
務必禁止不帶任何限制數據範圍條件的查詢語句。比如:我們當用戶在查詢訂單歷史的時候,我們可以控制在一個月的範圍內;
2. 讀/寫分離
經典的數據庫拆分方案,主庫負責寫,從庫負責讀;
3. 垂直分區
根據數據庫裏面數據表的相關性進行拆分。 例如,用戶表中既有用戶的登錄信息又有用戶的基本信息,可以將用戶表拆分成兩個單獨的表,甚至放到單獨的庫做分庫。
簡單來說垂直拆分是指數據表列的拆分,把一張列比較多的表拆分爲多張表。 如下圖所示,這樣來說大家應該就更容易理解了。
數據庫垂直分區
-
垂直拆分的優點: 可以使得列數據變小,在查詢時減少讀取的Block數,減少I/O次數。此外,垂直分區可以簡化表的結構,易於維護。
-
垂直拆分的缺點: 主鍵會出現冗餘,需要管理冗餘列,並會引起Join操作,可以通過在應用層進行Join來解決。此外,垂直分區會讓事務變得更加複雜;
4. 水平分區
保持數據表結構不變,通過某種策略存儲數據分片。這樣每一片數據分散到不同的表或者庫中,達到了分佈式的目的。 水平拆分可以支撐非常大的數據量。
水平拆分是指數據錶行的拆分,表的行數超過200萬行時,就會變慢,這時可以把一張的表的數據拆成多張表來存放。舉個例子:我們可以將用戶信息表拆分成多個用戶信息表,這樣就可以避免單一表數據量過大對性能造成影響。
數據庫水平拆分
水平拆分可以支持非常大的數據量。需要注意的一點是:分表僅僅是解決了單一表數據過大的問題,但由於表的數據還是在同一臺機器上,其實對於提升MySQL併發能力沒有什麼意義,所以 水平拆分最好分庫 。
水平拆分能夠 支持非常大的數據量存儲,應用端改造也少,但 分片事務難以解決 ,跨節點Join性能較差,邏輯複雜。《Java工程師修煉之道》的作者推薦 儘量不要對數據進行分片,因爲拆分會帶來邏輯、部署、運維的各種複雜度 ,一般的數據表在優化得當的情況下支撐千萬以下的數據量是沒有太大問題的。如果實在要分片,儘量選擇客戶端分片架構,這樣可以減少一次和中間件的網絡I/O。
下面補充一下數據庫分片的兩種常見方案:
-
客戶端代理: 分片邏輯在應用端,封裝在jar包中,通過修改或者封裝JDBC層來實現。 噹噹網的 Sharding-JDBC 、阿里的TDDL是兩種比較常用的實現。
-
中間件代理: 在應用和數據中間加了一個代理層。分片邏輯統一維護在中間件服務中。 我們現在談的 Mycat 、360的Atlas、網易的DDB等等都是這種架構的實現。
Mysql5.7 忘記了密碼--怎麼辦
1. 關閉正在運行的MySQL服務。
2. 打開DOS窗口,轉到mysql\bin目錄
3. 輸入mysqld --skip-grant-tables 回車
--skip-grant-tables 的意思是啓動MySQL服務的時候跳過權限表認證。
4. 再開一個DOS窗口(因爲剛纔那個DOS窗口已經不能動了),轉到mysql\bin目錄。
5. 輸入mysql回車,如果成功,將出現MySQL提示符 >。
6. 連接權限數據庫: use mysql; (別忘了最後加分號) 。
7. 改密碼:update user set password=password("123") where user="root"; (別忘了最後加分號) 。
如果修改密碼出現 **mysql修改密碼錯誤 ERROR 1054 (42S22)**
則使用 mysql>update mysql.user set authentication_string=password('123456') where user='root' and Host ='localhost';