脏读,不可重复读,幻读
MySQL事務隔離級別:
在介紹臟讀,不可重復讀,幻讀現象之前,我們先來了解MySQL的事務隔離級別,因為臟讀,不可重復讀,幻讀等現象都是由數據庫里的事務隔離級別來決定是否可能發生的。
在MySQL里共有四個隔離級別,分別是:Read uncommttied(可以讀取未提交數據)、Read committed(可以讀取已提交數據)、Repeatable read(可重復讀)、Serializable(可串行化)。
在MySQL數據庫里,默認的事務隔離級別是Repeatable read(可重復讀)。
使用select @@tx_isolation;?命令可以查看MySQL默認的事務隔離級別:
每個事務隔離級別會導致的數據現象:
但是這里有一點需要注意的是數據庫的默認引擎是InnoDB在使用InnoDB引擎下,即便設定的事務隔離級別是Repeatable read,也不會出現數據幻讀現象。
? -原因:MySQL InnoDB存儲引擎,實現的是基于多版本的并發控制協議——MVCC (Multi-Version Concurrency Control) 所以在Repeatable Read (RR)隔離級別下不存在幻讀。
臟讀現象:
在默認的事務隔離級別下,我們是無法讀取到未提交的數據的,在能夠讀取到未提交數據的事務隔離級別下,才會出現臟讀現象。臟讀就是指當一個事務正在訪問數據,并且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時,另外一個事務也訪問這個數據,然后使用了這個數據。因為這個數據是還沒有提交的數據,那么另外一個事務讀到的這個數據是臟數據(Dirty Data),依據臟數據所做的操作可能是不正確的。
簡而言之會出現臟讀現象就是因為用戶能夠讀取到未提交到數據里的數據,也即是無效的數據,然后對這些無效的臟數據進行了操作,所以這些操作都是無效或者錯誤的。
用言語來描述可能有點抽象、不好理解,下面我們打開兩個MySQL客戶端,來進行臟讀現象的實驗:
1.使用SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; 命令將兩個MySQL客戶端的事務隔離級別設定為Read uncommttied級別:
2.現在我們使用其中一個用戶,往表格里插入一條數據,但是不執行commit命令,會發現另一個用戶也能讀取到這個未提交的數據:
這就是臟讀現象,此現象稱之為臟讀因為讀取出來的是無效數據,無效數據就等于是垃圾數據垃圾就當然就是臟的所以才叫臟讀,而且如果我們以這個臟數據作為某些參數的話,必然會出現錯誤。
不可重復讀現象:
在一個事務內,多次讀同一個數據。在這個事務還沒有結束時,另一個事務也訪問該同一數據。那么,在第一個事務的兩次讀數據之間。由于第二個事務的修改,那么第一個事務讀到的數據可能不一樣,這樣就發生了在一個事務內兩次讀到的數據是不一樣的,因此稱為不可重復讀,即原始讀取不可重復。
?
下面我們通過實驗來看看不可重復讀現象:
1.使用SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;命令將兩個MySQL客戶端的事務隔離級別設定為Read committed,這是因為要避免出現臟讀現象:
2.現在我們使用其中一個用戶,修改表格里的一條數據,但是不執行commit命令,會發現另一個用戶不能讀取到這個未提交的數據:
3.但是用戶B執行commit命令后就不一樣了:
不可重復讀現象主要是指,在一個事務結束前(執行commit或rollback前),進行兩次或多次讀取同一個數據會出現不同的結果,所以稱為不可重復讀,因為重復讀取就會出現這種數據不一致的情況。
幻讀現象:
幻讀是指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的數據進行了修改,比如這種修改涉及到表中的“全部數據行”。同時,第二個事務也修改這個表中的數據,這種修改是向表中插入“一行新數據”。那么,以后就會發生操作第一個事務的用戶發現表中還存在沒有修改的數據行,就好象發生了幻覺一樣.一般解決幻讀的方法是增加范圍鎖RangeS,鎖定檢索范圍為只讀,這樣就避免了幻讀。
幻讀實際上和不可重復讀有一點類似,都是第二次或多次查詢的時候發現數據發生了變化,但是幻讀側重在表格里數據的數量上的變化,而且也是在事務生命周期內的查詢上發生的變化,所以有一點要注意的是:嚴格意義上只有當用戶A在事務生命周期內多次查詢數據時數據發生變化,才能算得上是不可重復讀或幻讀現象,如果用戶A在一個事務結束后接著在另一個新的事務里查詢后發現數據發生了變化,那么這就不算是不可重復讀或者幻讀。
下面我們通過實驗來看看幻讀現象:
? 1.因為實際上在InnoDB存儲引擎里的Repeatable read級別,已經解決了幻讀現象,所以我們不需要更改隔離級別,仍舊使用Read committed級別即可:
2.現在我們使用其中一個用戶,往表格了里表格里插入一條數據,但是不執行commit命令,同樣的會發現另一個用戶不能讀取到這個未提交的數據:
3.當用戶B commit之后用戶A再查詢就會發現多了一行數據:
然后用戶B把這條數據刪除了:
所以幻讀稱之為幻讀的原因就是這,在一個事務生命周期內的查詢上發生的表格數據數量上的變化,一下多了幾行數據,一下又少了幾行數據,跟活在夢一樣,分分鐘上下幾百萬。
不可重復讀和幻讀的區別:
不可重復讀強調的是每次讀取的是相同位置的數據,且該數據在另一個事務下被修改。注重的是修改。這個位置指的是哪一行、哪一個字段的數據。
幻讀強調的是第二次讀比第一次讀取時,內容多了或者少了幾行,注重的是新增和刪除。
?
Serializable級別:
完全串行化的讀,每次讀都需要獲得表級共享鎖,讀寫相互會相互互斥,這樣可以更好的解決數據一致性的問題,但是同樣會大大的降低數據庫的實際吞吐性能。所以該隔離級別因為損耗太大,一般很少在開發中使用,在此就不介紹了。
?
?
幻讀的實際應用例題:
以上介紹的那些現象并不是數據庫的BUG或者一些問題什么的,實際上有些業務需求就是需要這些數據現象來完成。例如幻讀現象,在車票、電影票鎖座等方面都有幻讀的應用例子。
例如假設在購買車票的時候,你一開始明明查詢只有三張票,但是一會再查一次就發現多了五張票,這就是幻讀的現象。因為別人查詢到這幾張票的時候這幾張票處于鎖定狀態,所以你就查詢不到,如果對方放棄購買的話,這些票又重新回到出售界面了,所以你第二次查詢的才會發現多了幾張票,這就是幻讀在實際生活中的一個應用例子。
?
現在我們編寫一個簡單的票務系統來演示幻讀的應用:
圖形界面代碼示例:
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("凱哥學堂-票務實驗"); 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("臥鋪數量:"); 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("查詢票務"); 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("確認購買"); 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;//?開啟事務 public?void?openTran()?throws?SQLException?{conn?=?DBManager.getDBManager(); conn.setAutoCommit(false);? //?設置事務隔離等級 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(); } }運行結果:
? 用戶B想買五張票,但是查詢的時候用戶B只能查到編號為6、7、8、9的四張車票,因為其他票都被用戶A鎖定了:
然后第二次查詢的時候用戶B發現能夠查到五張票了,這是因為用戶A放棄了購買,這些票又重新回到出售界面了,這就是幻讀的實際應用例子:
轉載于:https://blog.51cto.com/zero01/1977015
總結
以上是生活随笔為你收集整理的脏读,不可重复读,幻读的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JDBC接口
- 下一篇: python 下载小说