脏读,不可重复读,幻读
MySQL事務(wù)隔離級別:
在介紹臟讀,不可重復(fù)讀,幻讀現(xiàn)象之前,我們先來了解MySQL的事務(wù)隔離級別,因為臟讀,不可重復(fù)讀,幻讀等現(xiàn)象都是由數(shù)據(jù)庫里的事務(wù)隔離級別來決定是否可能發(fā)生的。
在MySQL里共有四個隔離級別,分別是:Read uncommttied(可以讀取未提交數(shù)據(jù))、Read committed(可以讀取已提交數(shù)據(jù))、Repeatable read(可重復(fù)讀)、Serializable(可串行化)。
在MySQL數(shù)據(jù)庫里,默認(rèn)的事務(wù)隔離級別是Repeatable read(可重復(fù)讀)。
使用select @@tx_isolation;?命令可以查看MySQL默認(rèn)的事務(wù)隔離級別:
每個事務(wù)隔離級別會導(dǎo)致的數(shù)據(jù)現(xiàn)象:
但是這里有一點需要注意的是數(shù)據(jù)庫的默認(rèn)引擎是InnoDB在使用InnoDB引擎下,即便設(shè)定的事務(wù)隔離級別是Repeatable read,也不會出現(xiàn)數(shù)據(jù)幻讀現(xiàn)象。
? -原因:MySQL InnoDB存儲引擎,實現(xiàn)的是基于多版本的并發(fā)控制協(xié)議——MVCC (Multi-Version Concurrency Control) 所以在Repeatable Read (RR)隔離級別下不存在幻讀。
臟讀現(xiàn)象:
在默認(rèn)的事務(wù)隔離級別下,我們是無法讀取到未提交的數(shù)據(jù)的,在能夠讀取到未提交數(shù)據(jù)的事務(wù)隔離級別下,才會出現(xiàn)臟讀現(xiàn)象。臟讀就是指當(dāng)一個事務(wù)正在訪問數(shù)據(jù),并且對數(shù)據(jù)進行了修改,而這種修改還沒有提交到數(shù)據(jù)庫中,這時,另外一個事務(wù)也訪問這個數(shù)據(jù),然后使用了這個數(shù)據(jù)。因為這個數(shù)據(jù)是還沒有提交的數(shù)據(jù),那么另外一個事務(wù)讀到的這個數(shù)據(jù)是臟數(shù)據(jù)(Dirty Data),依據(jù)臟數(shù)據(jù)所做的操作可能是不正確的。
簡而言之會出現(xiàn)臟讀現(xiàn)象就是因為用戶能夠讀取到未提交到數(shù)據(jù)里的數(shù)據(jù),也即是無效的數(shù)據(jù),然后對這些無效的臟數(shù)據(jù)進行了操作,所以這些操作都是無效或者錯誤的。
用言語來描述可能有點抽象、不好理解,下面我們打開兩個MySQL客戶端,來進行臟讀現(xiàn)象的實驗:
1.使用SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; 命令將兩個MySQL客戶端的事務(wù)隔離級別設(shè)定為Read uncommttied級別:
2.現(xiàn)在我們使用其中一個用戶,往表格里插入一條數(shù)據(jù),但是不執(zhí)行commit命令,會發(fā)現(xiàn)另一個用戶也能讀取到這個未提交的數(shù)據(jù):
這就是臟讀現(xiàn)象,此現(xiàn)象稱之為臟讀因為讀取出來的是無效數(shù)據(jù),無效數(shù)據(jù)就等于是垃圾數(shù)據(jù)垃圾就當(dāng)然就是臟的所以才叫臟讀,而且如果我們以這個臟數(shù)據(jù)作為某些參數(shù)的話,必然會出現(xiàn)錯誤。
不可重復(fù)讀現(xiàn)象:
在一個事務(wù)內(nèi),多次讀同一個數(shù)據(jù)。在這個事務(wù)還沒有結(jié)束時,另一個事務(wù)也訪問該同一數(shù)據(jù)。那么,在第一個事務(wù)的兩次讀數(shù)據(jù)之間。由于第二個事務(wù)的修改,那么第一個事務(wù)讀到的數(shù)據(jù)可能不一樣,這樣就發(fā)生了在一個事務(wù)內(nèi)兩次讀到的數(shù)據(jù)是不一樣的,因此稱為不可重復(fù)讀,即原始讀取不可重復(fù)。
?
下面我們通過實驗來看看不可重復(fù)讀現(xiàn)象:
1.使用SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;命令將兩個MySQL客戶端的事務(wù)隔離級別設(shè)定為Read committed,這是因為要避免出現(xiàn)臟讀現(xiàn)象:
2.現(xiàn)在我們使用其中一個用戶,修改表格里的一條數(shù)據(jù),但是不執(zhí)行commit命令,會發(fā)現(xiàn)另一個用戶不能讀取到這個未提交的數(shù)據(jù):
3.但是用戶B執(zhí)行commit命令后就不一樣了:
不可重復(fù)讀現(xiàn)象主要是指,在一個事務(wù)結(jié)束前(執(zhí)行commit或rollback前),進行兩次或多次讀取同一個數(shù)據(jù)會出現(xiàn)不同的結(jié)果,所以稱為不可重復(fù)讀,因為重復(fù)讀取就會出現(xiàn)這種數(shù)據(jù)不一致的情況。
幻讀現(xiàn)象:
幻讀是指當(dāng)事務(wù)不是獨立執(zhí)行時發(fā)生的一種現(xiàn)象,例如第一個事務(wù)對一個表中的數(shù)據(jù)進行了修改,比如這種修改涉及到表中的“全部數(shù)據(jù)行”。同時,第二個事務(wù)也修改這個表中的數(shù)據(jù),這種修改是向表中插入“一行新數(shù)據(jù)”。那么,以后就會發(fā)生操作第一個事務(wù)的用戶發(fā)現(xiàn)表中還存在沒有修改的數(shù)據(jù)行,就好象發(fā)生了幻覺一樣.一般解決幻讀的方法是增加范圍鎖RangeS,鎖定檢索范圍為只讀,這樣就避免了幻讀。
幻讀實際上和不可重復(fù)讀有一點類似,都是第二次或多次查詢的時候發(fā)現(xiàn)數(shù)據(jù)發(fā)生了變化,但是幻讀側(cè)重在表格里數(shù)據(jù)的數(shù)量上的變化,而且也是在事務(wù)生命周期內(nèi)的查詢上發(fā)生的變化,所以有一點要注意的是:嚴(yán)格意義上只有當(dāng)用戶A在事務(wù)生命周期內(nèi)多次查詢數(shù)據(jù)時數(shù)據(jù)發(fā)生變化,才能算得上是不可重復(fù)讀或幻讀現(xiàn)象,如果用戶A在一個事務(wù)結(jié)束后接著在另一個新的事務(wù)里查詢后發(fā)現(xiàn)數(shù)據(jù)發(fā)生了變化,那么這就不算是不可重復(fù)讀或者幻讀。
下面我們通過實驗來看看幻讀現(xiàn)象:
? 1.因為實際上在InnoDB存儲引擎里的Repeatable read級別,已經(jīng)解決了幻讀現(xiàn)象,所以我們不需要更改隔離級別,仍舊使用Read committed級別即可:
2.現(xiàn)在我們使用其中一個用戶,往表格了里表格里插入一條數(shù)據(jù),但是不執(zhí)行commit命令,同樣的會發(fā)現(xiàn)另一個用戶不能讀取到這個未提交的數(shù)據(jù):
3.當(dāng)用戶B commit之后用戶A再查詢就會發(fā)現(xiàn)多了一行數(shù)據(jù):
然后用戶B把這條數(shù)據(jù)刪除了:
所以幻讀稱之為幻讀的原因就是這,在一個事務(wù)生命周期內(nèi)的查詢上發(fā)生的表格數(shù)據(jù)數(shù)量上的變化,一下多了幾行數(shù)據(jù),一下又少了幾行數(shù)據(jù),跟活在夢一樣,分分鐘上下幾百萬。
不可重復(fù)讀和幻讀的區(qū)別:
不可重復(fù)讀強調(diào)的是每次讀取的是相同位置的數(shù)據(jù),且該數(shù)據(jù)在另一個事務(wù)下被修改。注重的是修改。這個位置指的是哪一行、哪一個字段的數(shù)據(jù)。
幻讀強調(diào)的是第二次讀比第一次讀取時,內(nèi)容多了或者少了幾行,注重的是新增和刪除。
?
Serializable級別:
完全串行化的讀,每次讀都需要獲得表級共享鎖,讀寫相互會相互互斥,這樣可以更好的解決數(shù)據(jù)一致性的問題,但是同樣會大大的降低數(shù)據(jù)庫的實際吞吐性能。所以該隔離級別因為損耗太大,一般很少在開發(fā)中使用,在此就不介紹了。
?
?
幻讀的實際應(yīng)用例題:
以上介紹的那些現(xiàn)象并不是數(shù)據(jù)庫的BUG或者一些問題什么的,實際上有些業(yè)務(wù)需求就是需要這些數(shù)據(jù)現(xiàn)象來完成。例如幻讀現(xiàn)象,在車票、電影票鎖座等方面都有幻讀的應(yīng)用例子。
例如假設(shè)在購買車票的時候,你一開始明明查詢只有三張票,但是一會再查一次就發(fā)現(xiàn)多了五張票,這就是幻讀的現(xiàn)象。因為別人查詢到這幾張票的時候這幾張票處于鎖定狀態(tài),所以你就查詢不到,如果對方放棄購買的話,這些票又重新回到出售界面了,所以你第二次查詢的才會發(fā)現(xiàn)多了幾張票,這就是幻讀在實際生活中的一個應(yīng)用例子。
?
現(xiàn)在我們編寫一個簡單的票務(wù)系統(tǒng)來演示幻讀的應(yīng)用:
圖形界面代碼示例:
import?java.awt.BorderLayout; import?java.awt.Color; import?java.awt.EventQueue; import?java.awt.Font; import?java.awt.event.ActionEvent; import?java.awt.event.ActionListener; import?java.sql.SQLException; import?java.util.Vector;import?javax.swing.JButton; import?javax.swing.JComboBox;import?javax.swing.JFrame; import?javax.swing.JLabel; import?javax.swing.JPanel; import?javax.swing.JScrollPane; import?javax.swing.JTable; import?javax.swing.JTextField; import?javax.swing.SwingConstants;public?class?PiaoWuSystem?extends?JFrame?{private?JTable?table; private?JComboBox?comboBox;/***?Launch?the?application*?*?@param?args*/ public?static?void?main(String?args[])?{ EventQueue.invokeLater(new?Runnable()?{ public?void?run()?{ try?{ PiaoWuSystem?frame?=?new?PiaoWuSystem(); frame.setVisible(true); }?catch?(Exception?e)?{ e.printStackTrace(); } } }); }public?Vector<String>?cols?=?new?Vector(); public?Vector<Vector<String>>?rows?=?new?Vector(); public?final?JLabel?label_2?=?new?JLabel();PiaoWuDB?piaoWuDB;/***?Create?the?frame*/ /***?*/ int?time?=?100;public?PiaoWuSystem()?{ super();Thread?thread?=?new?Thread(new?Runnable()?{public?void?run()?{ while?(true)?{ label_2.setText("有效時間:?"?+?time?+?"?秒"); try?{ Thread.sleep(1000); }?catch?(InterruptedException?e)?{ e.printStackTrace(); } time--; if?(time?==?0)?{ break; } } try?{ piaoWuDB.rollback(); rows.clear(); table.updateUI(); }?catch?(Exception?e)?{ //?TODO:?handle?exception }} }); thread.start();setResizable(false); setBounds(100,?100,?750,?389); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);final?JLabel?label?=?new?JLabel(); label.setFont(new?Font("微軟雅黑",?Font.BOLD,?24)); label.setHorizontalAlignment(SwingConstants.CENTER); label.setText("凱哥學(xué)堂-票務(wù)實驗"); getContentPane().add(label,?BorderLayout.NORTH);final?JPanel?panel?=?new?JPanel(); panel.setLayout(null); getContentPane().add(panel,?BorderLayout.CENTER);final?JLabel?label_1?=?new?JLabel(); label_1.setText("臥鋪數(shù)量:"); label_1.setBounds(10,?14,?66,?18); panel.add(label_1);comboBox?=?new?JComboBox(); comboBox.addItem("1"); comboBox.addItem("2"); comboBox.addItem("3"); comboBox.addItem("4"); comboBox.addItem("5"); comboBox.setBounds(82,?10,?66,?27); panel.add(comboBox);final?JButton?button?=?new?JButton(); button.addActionListener(new?ActionListener()?{ public?void?actionPerformed(final?ActionEvent?e)?{ if?(piaoWuDB?!=?null)?{ try?{ piaoWuDB.rollback(); }?catch?(SQLException?e1)?{ e1.printStackTrace(); } } time=30; try?{ piaoWuDB?=?new?PiaoWuDB(); piaoWuDB.openTran(); Vector<Vector<String>>?r?=?piaoWuDB.chaxun(Integer.parseInt(comboBox.getSelectedItem().toString())); rows.clear(); rows.addAll(r); table.updateUI(); }?catch?(Exception?e2)?{ e2.printStackTrace(); }} }); button.setText("查詢票務(wù)"); button.setBounds(628,?9,?106,?28); panel.add(button);label_2.setForeground(new?Color(255,?0,?0)); label_2.setFont(new?Font("微軟雅黑",?Font.BOLD,?15)); label_2.setHorizontalAlignment(SwingConstants.CENTER); label_2.setText("有效時間:"); label_2.setBounds(464,?10,?158,?27); panel.add(label_2);final?JScrollPane?scrollPane?=?new?JScrollPane(); scrollPane.setBounds(10,?52,?724,?234); panel.add(scrollPane);cols.add("編號"); cols.add("類型"); cols.add("鋪位");table?=?new?JTable(rows,?cols); scrollPane.setViewportView(table);final?JButton?button_1?=?new?JButton(); button_1.addActionListener(new?ActionListener()?{ public?void?actionPerformed(final?ActionEvent?e)?{try?{ piaoWuDB.rollback(); }?catch?(SQLException?e1)?{ //?TODO?Auto-generated?catch?block e1.printStackTrace(); } rows.clear(); table.updateUI();} }); button_1.setText("取消查詢"); button_1.setBounds(628,?292,?106,?28); panel.add(button_1);final?JButton?button_2?=?new?JButton(); button_2.addActionListener(new?ActionListener()?{ public?void?actionPerformed(final?ActionEvent?e)?{ try?{ piaoWuDB.commit(); }?catch?(SQLException?e1)?{ e1.printStackTrace(); } rows.clear(); table.updateUI(); } }); button_2.setText("確認(rèn)購買"); button_2.setBounds(516,?292,?106,?28); panel.add(button_2); // } }JDBC代碼示例:
import?java.sql.Connection; import?java.sql.ResultSet; import?java.sql.SQLException; import?java.sql.Statement; import?java.util.Vector;import?org.zero01.DBManager.DBManager;public?class?PiaoWuDB?{private?Connection?conn?=?null;//?開啟事務(wù) public?void?openTran()?throws?SQLException?{conn?=?DBManager.getDBManager(); conn.setAutoCommit(false);? //?設(shè)置事務(wù)隔離等級 conn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);}public?Vector<Vector<String>>?chaxun(int?num)?throws?SQLException?{ Statement?st?=?conn.createStatement(); ResultSet?rs?=?st.executeQuery("select?*?from?piaowu?where?state=1?limit?0,"?+?num); Vector<Vector<String>>?rows?=?new?Vector<Vector<String>>(); while?(rs.next())?{ st?=?conn.createStatement(); int?rowNum?=?st.executeUpdate("UPDATE?PIAOWU?SET?STATE=0?WHERE?PID="?+?rs.getInt(1)?+?"?AND?STATE=1"); if?(rowNum?>=?1)?{ Vector<String>?row?=?new?Vector(); row.add(rs.getInt(1)+""); row.add(rs.getString(2)); row.add(rs.getString(3)); rows.add(row); } } return?rows; }public?void?commit()?throws?SQLException?{ conn.commit(); conn.close(); }public?void?rollback()?throws?SQLException?{ conn.rollback(); conn.close(); } }運行結(jié)果:
? 用戶B想買五張票,但是查詢的時候用戶B只能查到編號為6、7、8、9的四張車票,因為其他票都被用戶A鎖定了:
然后第二次查詢的時候用戶B發(fā)現(xiàn)能夠查到五張票了,這是因為用戶A放棄了購買,這些票又重新回到出售界面了,這就是幻讀的實際應(yīng)用例子:
轉(zhuǎn)載于:https://blog.51cto.com/zero01/1977015
總結(jié)
以上是生活随笔為你收集整理的脏读,不可重复读,幻读的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JDBC接口
- 下一篇: ansible unarchive模块