當(dāng)多個(gè)用戶訪問同一份數(shù)據(jù)時(shí),一個(gè)用戶在更改數(shù)據(jù)的過程中,可能有其他用戶同時(shí)發(fā)起更改請(qǐng)求,為保證數(shù)據(jù)庫記錄的更新從一個(gè)一致性狀態(tài)變?yōu)榱硗庖粋€(gè)一致性狀態(tài),使用事務(wù)處理是非常必要的,事務(wù)具有以下四個(gè)特性:
成都創(chuàng)新互聯(lián)專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于成都網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè)、滎陽網(wǎng)絡(luò)推廣、小程序設(shè)計(jì)、滎陽網(wǎng)絡(luò)營銷、滎陽企業(yè)策劃、滎陽品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運(yùn)營等,從售前售中售后,我們都將竭誠為您服務(wù),您的肯定,是我們最大的嘉獎(jiǎng);成都創(chuàng)新互聯(lián)為所有大學(xué)生創(chuàng)業(yè)者提供滎陽建站搭建服務(wù),24小時(shí)服務(wù)熱線:13518219792,官方網(wǎng)址:www.cdcxhl.com
MySQL 提供了多種事務(wù)型存儲(chǔ)引擎,如 InnoDB 和 BDB 等,而 MyISAM 不支持事務(wù)。為了支持事務(wù),InnoDB 存儲(chǔ)引擎引入了與事務(wù)處理相關(guān)的 REDO 日志和 UNDO 日志,同時(shí)事務(wù)依賴于 MySQL 提供的鎖機(jī)制
事務(wù)執(zhí)行時(shí)需要將執(zhí)行的事務(wù)日志寫入日志文件,對(duì)應(yīng)的文件為 REDO 日志。當(dāng)每條 SQL 進(jìn)行數(shù)據(jù)更新操作時(shí),首先將 REDO 日志寫進(jìn)日志緩沖區(qū)。當(dāng)客戶端執(zhí)行 COMMIT 命令提交時(shí),日志緩沖區(qū)的內(nèi)容將被刷新到磁盤,日志緩沖區(qū)的刷新方式或者時(shí)間間隔可以通過參數(shù) innodb_flush_log_at_trx_commit 控制
REDO 日志對(duì)應(yīng)磁盤上的 ib_logifleN 文件,該文件默認(rèn)為 5MB,建議設(shè)置為 512MB,以便容納較大的事務(wù)。MySQL 崩潰恢復(fù)時(shí)會(huì)重新執(zhí)行 REDO 日志的記錄,恢復(fù)最新數(shù)據(jù),保證已提交事務(wù)的持久性
與 REDO 日志相反,UNDO 日志主要用于事務(wù)異常時(shí)的數(shù)據(jù)回滾,具體內(nèi)容就是記錄數(shù)據(jù)被修改前的信息到 UNDO 緩沖區(qū),然后在合適的時(shí)間將內(nèi)容刷新到磁盤
假如由于系統(tǒng)錯(cuò)誤或者 rollback 操作而導(dǎo)致事務(wù)回滾,可以根據(jù) undo 日志回滾到?jīng)]修改前的狀態(tài),保證未提交事務(wù)的原子性
與 REDO 日志不同的是,磁盤上不存在單獨(dú)的 UNDO 日志文件,所有的 UNDO 日志均存在表空間對(duì)應(yīng)的 .ibd 數(shù)據(jù)文件中,即使 MySQL 服務(wù)啟動(dòng)了獨(dú)立表空間
在 MySQL 中,可以使用 BEGIN 開始事務(wù),使用 COMMIT 結(jié)束事務(wù),中間可以使用 ROLLBACK 回滾事務(wù)。MySQL 通過 SET AUTOCOMMIT、START TRANSACTION、COMMIT 和 ROLLBACK 等語句支持本地事務(wù)
MySQL 定義了四種隔離級(jí)別,指定事務(wù)中哪些數(shù)據(jù)改變其他事務(wù)可見、哪些數(shù)據(jù)該表其他事務(wù)不可見。低級(jí)別的隔離級(jí)別可以支持更高的并發(fā)處理,同時(shí)占用的系統(tǒng)資源更少
InnoDB 系統(tǒng)級(jí)事務(wù)隔離級(jí)別可以使用以下語句設(shè)置:
查看系統(tǒng)級(jí)事務(wù)隔離級(jí)別:
InnoDB 會(huì)話級(jí)事務(wù)隔離級(jí)別可以使用以下語句設(shè)置:
查看會(huì)話級(jí)事務(wù)隔離級(jí)別:
在該隔離級(jí)別,所有事務(wù)都可以看到其他未提交事務(wù)的執(zhí)行結(jié)果。讀取未提交的數(shù)據(jù)稱為臟讀(Dirty Read),即是:首先開啟 A 和 B 兩個(gè)事務(wù),在 B 事務(wù)更新但未提交之前,A 事務(wù)讀取到了更新后的數(shù)據(jù),但由于 B 事務(wù)回滾,導(dǎo)致 A 事務(wù)出現(xiàn)了臟讀現(xiàn)象
所有事務(wù)只能看見已經(jīng)提交事務(wù)所做的改變,此級(jí)別可以解決臟讀,但也會(huì)導(dǎo)致不可重復(fù)讀(Nonrepeatable Read):首先開啟 A 和 B 兩個(gè)事務(wù),A事務(wù)讀取了 B 事務(wù)的數(shù)據(jù),在 B 事務(wù)更新并提交后,A 事務(wù)又讀取到了更新后的數(shù)據(jù),此時(shí)就出現(xiàn)了同一 A 事務(wù)中的查詢出現(xiàn)了不同的查詢結(jié)果
MySQL 默認(rèn)的事務(wù)隔離級(jí)別,能確保同一事務(wù)的多個(gè)實(shí)例在并發(fā)讀取數(shù)據(jù)時(shí)看到同樣的數(shù)據(jù)行,理論上會(huì)導(dǎo)致一個(gè)問題,幻讀(Phontom Read)。例如,第一個(gè)事務(wù)對(duì)一個(gè)表中的數(shù)據(jù)做了修改,這種修改會(huì)涉及表中的全部數(shù)據(jù)行,同時(shí)第二個(gè)事務(wù)也修改這個(gè)表中的數(shù)據(jù),這次的修改是向表中插入一行新數(shù)據(jù),此時(shí)就會(huì)發(fā)生操作第一個(gè)事務(wù)的用戶發(fā)現(xiàn)表中還有沒有修改的數(shù)據(jù)行
InnoDB 通過多版本并發(fā)控制機(jī)制(MVCC)解決了該問題:InnoDB 通過為每個(gè)數(shù)據(jù)行增加兩個(gè)隱含值的方式來實(shí)現(xiàn),這兩個(gè)隱含值記錄了行的創(chuàng)建時(shí)間、過期時(shí)間以及每一行存儲(chǔ)時(shí)間發(fā)生時(shí)的系統(tǒng)版本號(hào),每個(gè)查詢根據(jù)事務(wù)的版本號(hào)來查詢結(jié)果
通過強(qiáng)制事務(wù)排序,使其不可能相互沖突,從而解決幻讀問題。簡而言之,就是在每個(gè)讀的數(shù)據(jù)行上加上共享鎖實(shí)現(xiàn),這個(gè)級(jí)別會(huì)導(dǎo)致大量的超時(shí)現(xiàn)象和鎖競爭,一般不推薦使用
為了解決數(shù)據(jù)庫并發(fā)控制問題,如走到同一時(shí)刻客戶端對(duì)同一張表做更新或者查詢操作,需要對(duì)并發(fā)操作進(jìn)行控制,因此產(chǎn)生了鎖
共享鎖的粒度是行或者元組(多個(gè)行),一個(gè)事務(wù)獲取了共享鎖以后,可以對(duì)鎖定范圍內(nèi)的數(shù)據(jù)執(zhí)行讀操作
排他鎖的粒度與共享鎖相同,一個(gè)事務(wù)獲取排他鎖以后,可以對(duì)鎖定范圍內(nèi)的數(shù)據(jù)執(zhí)行寫操作
有兩個(gè)事務(wù) A 和 B,如果事務(wù) A 獲取了一個(gè)元組的共享鎖,事務(wù) B 還可以立即獲取這個(gè)元組的共享鎖,但不能獲取這個(gè)元組的排他鎖,必須等到事務(wù) A 釋放共享鎖之后。如果事務(wù) A 獲取了一個(gè)元組的排他鎖,事務(wù) B 不能立即獲取這個(gè)元組的共享鎖,也不能立即獲取這個(gè)元組的排他鎖,必須等到 A 釋放排他鎖之后
意向鎖是一種表鎖,鎖定的粒度是整張表,分為意向共享鎖和意向排他鎖。意向共享鎖表示一個(gè)事務(wù)有意對(duì)數(shù)據(jù)上共享鎖或者排他鎖。有意表示事務(wù)想執(zhí)行操作但還沒真正執(zhí)行
鎖的粒度主要分為表鎖和行鎖
表鎖的開銷最小,同時(shí)允許的并發(fā)量也是最小。MyISAM 存儲(chǔ)引擎使用該鎖機(jī)制。當(dāng)要寫入數(shù)據(jù)時(shí),整個(gè)表記錄被鎖,此時(shí)其他讀/寫動(dòng)作一律等待。一些特定的動(dòng)作,如 ALTER TABLE 執(zhí)行時(shí)使用的也是表鎖
行鎖可以支持最大的并發(fā),InnoDB 存儲(chǔ)引擎使用該鎖機(jī)制。如果要支持并發(fā)讀/寫,建議采用 InnoDB 存儲(chǔ)引擎
MySQL 事務(wù)
什么是事務(wù)?
MySQL 事務(wù)主要用于處理操作量大,復(fù)雜度高的數(shù)據(jù)。比如說,在人員管理系統(tǒng)中,你刪除一個(gè)人員,你既需要?jiǎng)h除人員的基本資料,也要?jiǎng)h除和該人員相關(guān)的信息,如信箱,文章等等,這樣,這些數(shù)據(jù)庫操作語句就構(gòu)成一個(gè)事務(wù)!
在 MySQL 中只有使用了 Innodb 數(shù)據(jù)庫引擎的數(shù)據(jù)庫或表才支持事務(wù)。
事務(wù)處理可以用來維護(hù)數(shù)據(jù)庫的完整性,保證成批的 SQL 語句要么全部執(zhí)行,要么全部不執(zhí)行。
事務(wù)用來管理 insert,update,delete 語句
一般來說,事務(wù)是必須滿足4個(gè)條件(ACID):原子性(Atomicity,或稱不可分割性)、一致性(Consistency)、隔離性(Isolation,又稱獨(dú)立性)、持久性(Durability)。
原子性:一個(gè)事務(wù)(transaction)中的所有操作,要么全部完成,要么全部不完成,不會(huì)結(jié)束在中間某個(gè)環(huán)節(jié)。事務(wù)在執(zhí)行過程中發(fā)生錯(cuò)誤,會(huì)被回滾(Rollback)到事務(wù)開始前的狀態(tài),就像這個(gè)事務(wù)從來沒有執(zhí)行過一樣。
一致性:在事務(wù)開始之前和事務(wù)結(jié)束以后,數(shù)據(jù)庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預(yù)設(shè)規(guī)則,這包含資料的精確度、串聯(lián)性以及后續(xù)數(shù)據(jù)庫可以自發(fā)性地完成預(yù)定的工作。
隔離性:數(shù)據(jù)庫允許多個(gè)并發(fā)事務(wù)同時(shí)對(duì)其數(shù)據(jù)進(jìn)行讀寫和修改的能力,隔離性可以防止多個(gè)事務(wù)并發(fā)執(zhí)行時(shí)由于交叉執(zhí)行而導(dǎo)致數(shù)據(jù)的不一致。事務(wù)隔離分為不同級(jí)別,包括讀未提交(Read uncommitted)、讀提交(read committed)、可重復(fù)讀(repeatable read)和串行化(Serializable)。
持久性:事務(wù)處理結(jié)束后,對(duì)數(shù)據(jù)的修改就是永久的,即便系統(tǒng)故障也不會(huì)丟失。
在 MySQL 命令行的默認(rèn)設(shè)置下,事務(wù)都是自動(dòng)提交的,即執(zhí)行 SQL 語句后就會(huì)馬上執(zhí)行 COMMIT 操作。因此要顯式地開啟一個(gè)事務(wù)務(wù)須使用命令 BEGIN 或 START TRANSACTION,或者執(zhí)行命令 SET AUTOCOMMIT=0,用來禁止使用當(dāng)前會(huì)話的自動(dòng)提交。
from 樹懶學(xué)堂 - 一站式數(shù)據(jù)知識(shí)平臺(tái)
看你是什么事務(wù),jdbc事務(wù),還是分布式事務(wù),還是容器事務(wù)
1,編程式事務(wù)管理(jdbc的事務(wù)是綁定在connection上的)
Connection conn = null;
try
{
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:oracle:thin:@host:1521:SID","username","password");
conn.setAutoCommit(false); //取消自動(dòng)提交
PreparedStatement ps = conn.prepareCall("update something");
ResultSet rs = ps.executeQuery();
conn.commit(); //手動(dòng)提交
}
catch (Exception e)
{
conn.rollback();
e.printStackTrace();
}
finally
{
conn.close();
}
2,聲明式事務(wù)
先在工程的application.xml配置文件中添加如下代碼,開啟事務(wù)
!-- 聲明式事務(wù)控制配置 --
tx:annotation-driven transaction-manager="txManager"/
bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
property name="datasource" ref="bassDataSource"/property
/bean
然后在你需要開啟事務(wù)的接口前面添加注解
@Transactional(rollbackFor = IOException.class)
public void add(String name) throws IOException
{
System.out.println("可以再類里和方法里面添加事務(wù)注解0~0");
throw new IOException();
}
直接調(diào)用接口方法就好
分布式事務(wù)處理(mysql貌似在5.X之后才支持) 的話,
1.可以直接使用spring+atomikos框架進(jìn)行管理
參考:
就不貼測試代碼了,自己看著配置吧
2,使用JTA(Java Transaction API)進(jìn)行分布式事務(wù)管理(測試代碼如下)
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
//分布式事務(wù)處理
public class transferAccount
{
@SuppressWarnings("null")
public void testTransferAccount()
{
UserTransaction userts = null;
Connection connA = null;
PreparedStatement psA = null;
InitialContext context = null;
Connection connB = null;
PreparedStatement psB = null;
try
{
//獲得事務(wù)管理對(duì)象
userts = (UserTransaction) context.lookup("java:comp/UserTransaction");
//獲取兩個(gè)數(shù)據(jù)庫
connA = getDataSourceA().getConnection();
connB = getDataSourceB().getConnection();
//開啟事務(wù)
userts.begin();
//sql語句
psA = connA.prepareStatement("我加1");
psB = connB.prepareStatement("我減1");
//執(zhí)行sql
psA.executeUpdate();
psB.executeUpdate();
//事務(wù)提交
userts.commit();
} catch (Exception e)
{
try
{
userts.rollback();
} catch (IllegalStateException | SecurityException
| SystemException e1)
{
e1.printStackTrace();
}
e.printStackTrace();
}
finally
{
try
{
psA.close();
psB.close();
connA.close();
connB.close();
} catch (SQLException e)
{
e.printStackTrace();
}
}
}
public DataSource getDataSourceA()
{
MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setDatabaseName("mysql");
dataSource.setServerName("server");
dataSource.setPortNumber(1433);
dataSource.setUser("test");
dataSource.setPassword("test");
return dataSource;
}
public DataSource getDataSourceB()
{
MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setDatabaseName("mysql");
dataSource.setServerName("server");
dataSource.setPortNumber(1435);
dataSource.setUser("test1");
dataSource.setPassword("test1");
return dataSource;
}
}
1、啟動(dòng)Navicat for MySQL,新建數(shù)據(jù)庫連接,打開數(shù)據(jù)庫,可能有點(diǎn)啰嗦。
2、可以先查看定時(shí)任務(wù)的是否開啟。通過以下命令:show variables like '%sche%';
3、如果其設(shè)置值為為 OFF 或 0 ,通過執(zhí)行下列語句,來開啟event_scheduler,set global event_scheduler =1,把設(shè)置為設(shè)為ON 或 1。
4、新建一個(gè)函數(shù) ,取名為update_qiandao ,內(nèi)容為 ?UPDATE week7_user SET isqiandao = 0
5、參照上圖,新建一個(gè)事件 ?,在定義里寫 call update_qiandao() ?--也就是之前定義的函數(shù)
在“計(jì)劃”選項(xiàng)卡中設(shè)置 執(zhí)行時(shí)間,這里我選擇的是每天。
6、完成結(jié)束