MySQL/InnoDB的併發插入Concurrent Insertjava
表people建表語句:ENGINE=InnoDBmysql
CREATE TABLE people ( person_id BIGINT NOT NULL AUTO_INCREMENT, first_name VARCHAR(20), last_name VARCHAR(20), PRIMARY KEY (person_id) );
兩個會話:會話一和會話二sql
在會話一中,執行以下sql語句:數據庫
mysql> use local_database; Database changed mysql> select @@autocommit; +--------------+ | @@autocommit | +--------------+ | 1 | +--------------+ 1 row in set (0.00 sec) mysql> set autocommit = 0; Query OK, 0 rows affected (0.00 sec) mysql> select @@autocommit; +--------------+ | @@autocommit | +--------------+ | 0 | +--------------+ 1 row in set (0.00 sec) mysql> select * from people; +-----------+------------+-----------+ | person_id | first_name | last_name | +-----------+------------+-----------+ | 1 | 1111 | 1111 | +-----------+------------+-----------+ 1 row in set (0.00 sec) mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert into people (first_name,last_name) values ('1111','1111'); Query OK, 1 row affected (0.12 sec) mysql> select * from people; +-----------+------------+-----------+ | person_id | first_name | last_name | +-----------+------------+-----------+ | 1 | 1111 | 1111 | | 3 | 1111 | 1111 | +-----------+------------+-----------+ 2 rows in set (0.00 sec)
上面這些sql 語句最終的操做就是手動開啓了一個事務,而後提交了一個insert語句,注意沒有手動提交事務。。多線程
此時在另外一個會話二中進行以下操做併發
mysql> use local_database; Database changed mysql> select @@autocommit; +--------------+ | @@autocommit | +--------------+ | 1 | +--------------+ 1 row in set (0.00 sec) mysql> set autocommit = 0; Query OK, 0 rows affected (0.00 sec) mysql> select @@autocommit; +--------------+ | @@autocommit | +--------------+ | 0 | +--------------+ 1 row in set (0.00 sec) mysql> select * from people; +-----------+------------+-----------+ | person_id | first_name | last_name | +-----------+------------+-----------+ | 1 | 1111 | 1111 | +-----------+------------+-----------+ 1 row in set (0.00 sec) mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert into people (first_name,last_name) values ('2222','2222'); Query OK, 1 row affected (0.00 sec) mysql> select * from people; +-----------+------------+-----------+ | person_id | first_name | last_name | +-----------+------------+-----------+ | 1 | 1111 | 1111 | | 4 | 2222 | 2222 | +-----------+------------+-----------+ 2 rows in set (0.00 sec)
上面的sql語句最終的操做是手動開啓了一個事務,執行了insert語句,而沒有提交事務,而後select查看只會看到當前會話的操做結果,而沒有會話一的操做結果。這就是mysql默認事務隔離級別——可重複讀。函數
作到這裏,我驗證的不是可重複讀的事務隔離級別,我其實想驗證的是會話在事務內執行insert語句會不會給表加鎖(會給表加鎖,AUTO-INC lock),經過結果顯示,可知,不一樣的會話在事務內執行insert語句,而不會阻塞其餘會話事務內的insert語句。測試
但最終我產生了疑問,MySQL/InnoDB是如何處理併發插入的。spa
提及auto_increment的併發插入,就要理解auto_increment的機制了。見文章:.net
http://my.oschina.net/xinxingegeya/blog/341991
http://my.oschina.net/xinxingegeya/blog/342075
下面我寫了兩個程序測試併發插入,一個是單線程的寫入10w條數據,一個是100條線程單個線程寫入1000條數據。能夠運行一下程序作一個對比。
建表語句
CREATE TABLE people_thread ( person_id BIGINT NOT NULL AUTO_INCREMENT, first_name VARCHAR(20), last_name VARCHAR(20), thread_name VARCHAR(20), PRIMARY KEY (person_id) );
總之仍是多線程的效率比較高:測試數據以下
單線程的運行時間爲:-- 16656ms
多線程的最長的運行時間爲:-- 12632ms
JDBCTest2.java
package com.lyx.other; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; public class JDBCTest2 { public static void main(String args[]) throws SQLException { long startTime = System.currentTimeMillis(); // 獲取開始時間 Connection conn = null; PreparedStatement ps = null; String sql = "insert into people_thread (first_name ,last_name ," + "thread_name) values (?,?,?)"; try { conn = getConnection(); conn.setAutoCommit(false); ps = conn.prepareStatement(sql); for (int i = 0; i < 100000; i++) { ps.setString(1, Integer.toString(i)); ps.setString(2, Integer.toString(i)); ps.setString(3, Integer.toString(i)); ps.addBatch(); // 一批提交一次 if (i % 10 == 0) { ps.executeBatch(); ps.clearBatch(); } } ps.executeBatch(); conn.commit(); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } finally { if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } long endTime = System.currentTimeMillis(); // 獲取結束時間 System.out.println("程序運行時間: " + (endTime - startTime) + "ms"); } /* 獲取數據庫鏈接的函數 */ public static Connection getConnection() { Connection con = null; // 建立用於鏈接數據庫的Connection對象 try { Class.forName("com.mysql.jdbc.Driver");// 加載Mysql數據驅動 con = DriverManager.getConnection( "jdbc:mysql://localhost:3306/local_database", "root", "034039");// 建立數據鏈接 } catch (Exception e) { System.out.println("數據庫鏈接失敗" + e.getMessage()); } return con; // 返回所創建的數據庫鏈接 } }
JDBCTest3.java
package com.lyx.other; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; public class JDBCTest3 { public static void main(String[] args) { // 建立10個線程執行插入操做 for (int i = 0; i < 100; i++) { final int n = i; Runnable task = new Runnable() { public void run() { Thread.currentThread().setName("thread_" + n); insertByBatch(Thread.currentThread().getName()); } }; Thread thread = new Thread(task); thread.start(); } } public static void insertByBatch(String name) { long startTime = System.currentTimeMillis(); // 獲取開始時間 Connection conn = null; PreparedStatement ps = null; String sql = "insert into people_thread (first_name ,last_name ," + "thread_name) values (?,?,?)"; try { conn = getConnection(); conn.setAutoCommit(false); ps = conn.prepareStatement(sql); for (int i = 0; i < 1000; i++) { ps.setString(1, Integer.toString(i)); ps.setString(2, Integer.toString(i)); ps.setString(3, name); ps.addBatch(); // 一批提交一次 if (i % 10 == 0) { ps.executeBatch(); ps.clearBatch(); } } ps.executeBatch(); conn.commit(); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } finally { if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } long endTime = System.currentTimeMillis(); // 獲取結束時間 System.out.println("程序運行時間: " + (endTime - startTime) + "ms"); } /* 獲取數據庫鏈接的函數 */ public static Connection getConnection() { Connection con = null; // 建立用於鏈接數據庫的Connection對象 try { Class.forName("com.mysql.jdbc.Driver");// 加載Mysql數據驅動 con = DriverManager.getConnection( "jdbc:mysql://localhost:3306/local_database", "root", "034039");// 建立數據鏈接 } catch (Exception e) { System.out.println("數據庫鏈接失敗" + e.getMessage()); } return con; // 返回所創建的數據庫鏈接 } }
============END============