MySQL經過BINLOG記錄執行成功的INSERT,UPDATE,DELETE等DML語句。並由此實現數據庫的恢復(point-in-time)和複製(其原理與恢復相似,經過複製和執行二進制日誌使一臺遠程的MySQLl數據庫,多稱爲slave,進行實時同步)。MySQL 5.5.x之後的版本支持3種日誌格式。經過binlog_format參數設置。該參數影響了記錄二進制日誌的格式,十分重要。php
1.STATEMENT格式和以前的MySQL版本同樣,二進制日誌文件記錄的是日誌的邏輯SQL語句。html
2.ROW格式記錄的再也不是簡單的SQL語句,而是記錄表的每行記錄更改的狀況。mysql
3.在MIXED格式下,MySQL默認採用STATEMENT格式進行二進制日誌文件的記錄。可是在一些特殊狀況下會使用ROW格式,可能的狀況以下:web
(1)表的存儲引擎爲NDB,這時對錶的DML操做都會以ROW格式記錄。算法
(2)使用了UUID(),USER(),CURRENT_USER(),FOUND_ROWS(),ROW_COUNT()等不肯定函數。sql
(3) 使用了INSERT DELAY語句。數據庫
(4)使用了用戶自定義函數(UDF).安全
(5)使用了臨時表(temporary table) 。session
對於基於語句的日誌格式(STATEMENT)的恢復和複製而言,因爲MySQL的BINLONG是按照事務(transaction)提交(committed)的前後順序記錄的,所以要正確恢復或者複製數據,就必須知足:在一個事務未提交前,其餘併發事務不能插入知足其鎖定條件的任何記錄,也就是不容許出現幻讀(Phantom Problem)。這已經超過了ISO/ANSI SQL92"可重複讀(Repeatable Read)"隔離級別的要求,其實是要求事務要串行化。這也是許多狀況下,InnoDB要用到Next-Key Lock鎖的緣由,好比用在範圍條件更新記錄時,不管是在Read Committed或者是Repeatable Read隔離級別下,InnoDB都要使用Next-key Lock鎖。既然說到Next-key Lock鎖機制,我這裏簡單說一下,演示各類效果就讓童鞋們本身去測試了^_^併發
mysql> select * from source_tab; +------+------+--------+ | id | age | name | +------+------+--------+ | 1 | 24 | yayun | | 2 | 24 | atlas | | 3 | 25 | david | | 4 | 24 | dengyy | +------+------+--------+ 4 rows in set (0.00 sec) mysql> select * from target_tab; Empty set (0.00 sec) mysql> desc source_tab; +-------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+-------+ | id | int(11) | YES | | NULL | | | age | int(11) | YES | | NULL | | | name | varchar(20) | YES | | NULL | | +-------+-------------+------+-----+---------+-------+ 3 rows in set (0.00 sec) mysql> desc target_tab; +-------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+-------+ | id | int(11) | YES | | NULL | | | age | int(11) | YES | | NULL | | | name | varchar(20) | YES | | NULL | | +-------+-------------+------+-----+---------+-------+ 3 rows in set (0.00 sec) mysql>
CTAS操做給原表加鎖的例子
session1操做
mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from source_tab; +------+------+--------+ | id | age | name | +------+------+--------+ | 1 | 24 | yayun | | 2 | 24 | atlas | | 3 | 25 | david | | 4 | 24 | dengyy | +------+------+--------+ 4 rows in set (0.00 sec) mysql> insert into target_tab select * from source_tab where name='yayun'; #該語句執行之後,session2中的update操做將會等待 Query OK, 1 row affected (0.00 sec) Records: 1 Duplicates: 0 Warnings: 0 mysql> commit; Query OK, 0 rows affected (0.04 sec) mysql>
session2操做
mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from source_tab; +------+------+--------+ | id | age | name | +------+------+--------+ | 1 | 24 | yayun | | 2 | 24 | atlas | | 3 | 25 | david | | 4 | 24 | dengyy | +------+------+--------+ 4 rows in set (0.00 sec) mysql> update source_tab set name='dengyayun' where name='yayun'; #一直等待,除非session1執行commit提交。 Query OK, 1 row affected (49.24 sec) #能夠看見用了49秒,這就是在等待session1提交,當session1提交後,順利更新 Rows matched: 1 Changed: 1 Warnings: 0 mysql> commit; Query OK, 0 rows affected (0.00 sec) mysql>
在上面示例中,只是簡單的讀source_tab表的數據,至關於執行一個普通的SELECT語句,用一致性讀就能夠了。Oracle正是這麼作的,它經過MVCC技術實現的多版本併發控制實現一致性讀,不須要給source_tab加任何鎖。你們都知道InnoDB也實現了多版本併發控制(MVCC),對普通的SELECT一致性讀,也不須要加任何鎖;可是這裏InnoDB卻給source_tab表加了共享鎖,並無使用多版本一致性讀技術。
MySQL爲何這麼作呢?why?其緣由仍是爲了保證恢復和複製的正確性。由於在不加鎖的狀況下,若是上述語句執行過程當中,其餘事務對原表(source_tab)作了更新操做,就可能致使數據恢復結果錯誤。爲了演示錯誤的發生,再重複上面的例子,先將系統變量innodb_locks_unsafe_for_binlog的值設爲"on",默認值是off。
innodb_locks_unsafe_for_binlog
其沒法動態修改,須要修改配置文件,演示以下:
CTAS操做不給原表加鎖帶來的安全問題
mysql> show variables like 'binlog_format'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | binlog_format | MIXED | +---------------+-------+ 1 row in set (0.00 sec) mysql> show variables like 'innodb_locks_unsafe%'; +--------------------------------+-------+ | Variable_name | Value | +--------------------------------+-------+ | innodb_locks_unsafe_for_binlog | ON | +--------------------------------+-------+ 1 row in set (0.00 sec) mysql>
session1操做
mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from source_tab where id=1; +------+------+-----------+ | id | age | name | +------+------+-----------+ | 1 | 24 | dengyayun | +------+------+-----------+ 1 row in set (0.00 sec) mysql> insert into target_tab select * from source_tab where id=1; Query OK, 1 row affected (0.00 sec) Records: 1 Duplicates: 0 Warnings: 0 mysql> commit; #插入操做後提交 Query OK, 0 rows affected (0.01 sec) mysql> select * from source_tab where name='good yayun'; #此時查看數據,target_tab中能夠插入source_tab更新前的結果,這複合應用邏輯 +------+------+------------+ | id | age | name | +------+------+------------+ | 1 | 24 | good yayun | +------+------+------------+ 1 row in set (0.00 sec) mysql> select * from target_tab; +------+------+-----------+ | id | age | name | +------+------+-----------+ | 1 | 24 | dengyayun | +------+------+-----------+ 1 row in set (0.00 sec)
session2操做
mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from source_tab where id=1; +------+------+-----------+ | id | age | name | +------+------+-----------+ | 1 | 24 | dengyayun | +------+------+-----------+ 1 row in set (0.00 sec) mysql> update source_tab set name='good yayun' where id=1; # session1未提交,能夠對session1中的select記錄進行更新操做 Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> commit; # 更新操做先提交 Query OK, 0 rows affected (0.02 sec) mysql> select * from source_tab where name='good yayun'; +------+------+------------+ | id | age | name | +------+------+------------+ | 1 | 24 | good yayun | +------+------+------------+ 1 row in set (0.00 sec) mysql> select * from target_tab; +------+------+-----------+ | id | age | name | +------+------+-----------+ | 1 | 24 | dengyayun | +------+------+-----------+ 1 row in set (0.00 sec) mysql>
從上面的測試結果能夠發現,設置系統變量innodb_locks_unsafe_for_binlog的值爲"ON"後,innodb再也不對原表(source_tab)加鎖,結果也符合應用的邏輯,可是若是咱們分析一下BINLOG內容,就能夠發現問題所在
[root@MySQL-01 mysql]# mysqlbinlog mysql-bin.000120 | grep -A 20 'update source_tab' update source_tab set name='good yayun' where id=1 /*!*/; # at 468 #140401 2:04:12 server id 1 end_log_pos 495 Xid = 74 COMMIT/*!*/; # at 495 #140401 2:04:23 server id 1 end_log_pos 563 Query thread_id=5 exec_time=0 error_code=0 SET TIMESTAMP=1396289063/*!*/; BEGIN /*!*/; # at 563 #140401 2:02:42 server id 1 end_log_pos 684 Query thread_id=5 exec_time=0 error_code=0 SET TIMESTAMP=1396288962/*!*/; insert into target_tab select * from source_tab where id=1 /*!*/; # at 684 #140401 2:04:23 server id 1 end_log_pos 711 Xid = 73 COMMIT/*!*/; DELIMITER ; # End of log file ROLLBACK /* added by mysqlbinlog */; [root@MySQL-01 mysql]#
能夠清楚的看到在BINLOG的記錄中,更新操做的位置在INSERT......SELECT以前,若是使用這個BINLOG進行數據庫恢復,恢復的結果則與實際的應用邏輯不符;若是進行復制,就會致使主從數據不一致!
經過上面的例子,相信童鞋們不難理解爲何MySQL在處理
"INSERT INTO target_tab SELECT * FROM source_tab WHERE...."
"CREATE TABLE new_tab....SELECT.....FROM source_tab WHERE...."
時要給原表(source_tab)加鎖,而不是使用對併發影響最小的多版本數據來實現一致性讀。還要特別說明的是,若是上述語句的SELECT是範圍條件,innodb還會給原表加上Next-Key Lock鎖。
所以,INSERT....SELECT和CREATE TABLE....SELECT.....語句,可能會阻止對原表的併發更新。若是查詢比較複雜,會照成嚴重的性能問題,生產環境須要謹慎使用。
總結以下:
若是應用中必定要用這種SQL來實現業務邏輯,又不但願對源表的併發更新產生影響,可使用下面3種方法:
1.將innodb_locks_unsafe_for_binlog的值設置爲"ON",強制MySQL使用多版本數據一致性讀。但付出的代價是可能沒法使用BINLOG正確的進行數據恢復或者主從複製。所以,此方法是不推薦使用的。
2.經過使用SELECT * FROM source_tab ..... INTO OUTFILE 和LOAD DATA INFILE.....語句組合來間接實現。採用這種放鬆MySQL不會給(源表)source_tab加鎖。
3.使用基於行(ROW)的BINLOG格式和基於行的數據的複製。此方法是推薦使用的方法。
參考資料:
https://www.facebook.com/note.php?note_id=131719925932
http://dev.mysql.com/doc/refman/5.0/en/innodb-parameters.html#sysvar_innodb_locks_unsafe_for_binlog