事务:Mysql、Spring

一、事务:

一系列的操作合并一起称为事务。

事务四特性(ACID)

A原子性:  事务是最小单元,不可再分隔的一个整体。
C一致性:  事务中的方法要么同时成功,要么都不成功,要不都失败。
I隔离性:  多个事务操作数据库中同一个记录或多个记录时,对事务进行隔离开来有序执行。
D持久性:  事务成功时,操作的结果永久的写入到数据库磁盘中。

五种数据库BUG读

脏读:(行数据写不加锁)
   A线程写时不加锁,B线程读A线程未提交的数据。A回滚,B线程之前读到的数据为无效数据。
不可重复读:(行数据读不加锁)
   A事务第一次读数据,B线程改操作。A第二次读取数据,同一事物内两次读取数据不一致。
第一类事务丢失:(回滚丢失)
   A事务撤销时,在A事务开始和结束的B事务也抹杀了。无视B的存在。
第二类事务丢失:(提交覆盖丢失)
   A事务覆盖B事务已提交的数据,造成B事务操作丢失。
幻读:(表数据读不加锁)
   主要指两次读取表的总数不一致。

四种隔离级别

1.读未提交数据(read uncommitted)(不加锁)
    允许事务读取未被其他事务提交的变更
2.读已提交数据(read committed)(写加锁,读不加锁)
    只允许事务读取已经被其他事务提交的变更
3.可重复读(repeatable read)(写、读加锁,默认)
    确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新。
4.串行化(serializable)(对表级加锁)
    读操作加表级读锁至事务结束。可以禁止幻读。会导致大量的操作超时和锁竞争,大大降低数据库的性
事务隔离级别
隔离级别   脏    读不可重复读   幻象读第一类丢失更新第二类丢失更新
READ UNCOMMITED允许允许允许不允许允许
READ COMMITTED不允许允许允许不允许允许
REPEATABLE READ不允许不允许允许不允许不允许
SERIALIZABLE不允许不允许不允许不允许不允许

二、Spring事务:

Spring用ThreadLocal为每个线程做了独立的副本,同时维护事务配置的属性和运行状态信息。事务同步管理器是Spring事务管理的基石。

Spring事务传播行为

传播行为类型  说  明  
PROPAGATION_REQUIRED如果没有当前事务,则新建一个事务;如果已存在一个事务,则加入这个事务中。
PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,以非事务方式运行。
PROPAGATION_MANDATORY使用当前事务。当前没有事务,抛异常。
PROPAGATION_REQUIRES_NEW新建事务。如果当前存在事务,则把当前事务挂起。
PROPAGATION_NOT_SUPPORTED以非事务方式操作。如果当前存在事务,则把事务挂起。
PROPAGATION_NEVER以非事务方式执行。如果当前存在事务,则抛异常。
PROPAGATION_NESTED如果当前存在事务,则嵌套事务内运行;当前无事务,则新建事务。

1.编程式事务管理

用TransactionTemplate或者直接使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate。

引入依赖

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.9.RELEASE</version>
        </dependency>

 代码

    @Autowired
    private PlatformTransactionManager transactionManager;

    @Autowired
    private TransactionTemplate transactionTemplate;

    public void testTransactionManager() {
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
        try {
            // ....  业务代码
            transactionManager.commit(status);
        } catch (Exception e) {
            transactionManager.rollback(status);
        }
    }

    public void testTransactionTemplate() {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                try {
                    // ....  业务代码
                } catch (Exception e) {
                    //回滚
                    transactionStatus.setRollbackOnly();
                }
            }
        });
    }

2.声明式事务管理

通过AOP在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或回滚。

 引入依赖

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.9.RELEASE</version>
        </dependency>

代码

@Transactional(propagation= Propagation.REQUIRED,isolation =Isolation.READ_UNCOMMITTED,timeout=30,readOnly=true,rollbackFor=RuntimeException.class)
public void testTransaction(){
        // ....  业务代码
}

注意: protected、private修饰的方法上使用 @Transactional注解,虽然事务无效,但不会有任何报错。

三、Mysql事务

Mysql的事务管理默认关闭(自动事务)。在Innodb中,每开启一个事务,都会为该session分配一个事务对象。

查询当前有多少事务正在运行。
select * from information_schema.innodb_trx;
查看自动事务的开启情况
show variables like 'autocommit';
查看当前会话隔离级别
select @@tx_isolation;
查看系统当前隔离级别
select @@global.tx_isolation;
设置当前会话隔离级别
set session transaction isolation level read uncommitted ;
设置系统当前隔离级别
set global transaction isolation level read repeatable ;

Mysql锁机制

1.表锁

表级锁是mysql粒度最大的锁,对整张表加锁,资源开销比行锁少,不会出现死锁,发生锁冲突的概率大。系统负面影响最小,获取锁和释放锁的速度快。
MyISAM只支持表锁,因此性能相对Innodb来说相对降低,Innodb默认是行锁但也支持表锁。

2.行锁

行锁的是mysql锁中粒度最小的一种锁,因为锁的粒度很小,所以资源争抢的概率也最小,并发性能最大,但是会造成死锁,每次加锁和释放锁的开销也大。目前主要是Innodb用行锁,行锁按照使用方式也分为共享锁(S锁、读锁)和排它锁(X锁、写锁)。

行锁—共享锁(S锁,读锁)

若事务A对数据甲加上S锁,则事务A可以读数据甲但不能改,其他事务只能再对数据甲加S锁,而不能加X锁,直到事务A释放数据甲上的S锁。保证了其他事务可以读数据甲,但在事务A释放数据甲上的S锁之前不能对数据甲做修改。用法:

select ... lock in share mode;

行锁—排它锁(X锁,写锁)

若事务A对数据甲加上X锁,事务A可以读数据甲也可以修改数据甲,其他事务不能再对数据甲加任何锁,直到事务A释放数据甲上的锁。保证了其他事务在事务A释放数据甲上的锁之前不能再读取和修改数据甲。
用法:

 select ... for update;

乐观锁(虚拟锁)

需要自己实现。在表中的数据进行操作时(更新),先给数据表加一个版本(version)字段,每操作一次,将那条记录的版本号加1。也就是先查询出那条记录,获取出version字段,如果要对那条记录进行操作(更新),则先判断此刻version的值是否与刚刚查询出来时的version的值相等,
相等则执行更新;不相等,则说明已有其他程序对其进行操作了,不进行更新操作。
例:

select data AS old_data, version AS old_version FROM …;
根据获取的数据进行业务操作,得到new_data和new_version
update SET data = new_data, version = new_version WHERE version = old_version

if (updated row > 0) {
    // 乐观锁获取成功,操作完成
} else {
    // 乐观锁获取失败,回滚并重试
}

    


悲观锁(虚拟锁)

首先用set autocommit=0关闭mysql的autoCommit属性。查询出数据之后要将该数据锁定。关闭自动提交后,需手动开启事务。

例:

    //1.开始事务
    begin; 或者 start transaction;
    //2.查询出商品信息,然后通过for update锁定数据防止其他事务修改
    select status from t_goods where id=1 for update;
    //3.根据商品信息生成订单
    insert into t_orders (id,goods_id) values (null,1);
    //4.修改商品status为2
    update t_goods set status=2;
    //4.提交事务
    commit; --执行完毕,提交事务

间隙锁(锁索引记录的范围)

MVCC(多版本并发控制)

InnoDB给每行增加两个隐藏字段来实现MVCC为记录数据行的创建事务的版本号、记录行的过期事务的版本号(删除时间),每开启一个新事务,事务的版本号就递增。
在RR级别下: SELECT读取创建版本小于或等于当前事务版本号,且删除版本为空或大于当前事务版本号的记录。这样可以保证在读取之前记录是存在的。
查找数据行版本早于当前事务版本的数据行(行的系统版本号小于或者等于当前事务版本号)这样可以确保事务读取的行要么在事务开始之前就已经存在了,要么是事务本身插入或者修改的。
查找事务删除版本号要么为null,要么大于当前事务版本号。确保取出来的行记录在事务开始前没有被删除。

ACID事务的实现(回滚日志)

SQL Server由两个文件组成:数据库文件、日志文件, 日志文件比数据库文件大写操作都先写日志。执行事务时会记录下这个事务的redo日志并把日志文件写入磁盘,然后才操作数据库。
恢复机制通过回滚日志(undo log)实现,是逻辑日志,当用回滚日志,会按日志逻辑将数据库中的修改撤掉,如每条 insert 都对应了一条 delete, update对应一条相反的 update 语句。

undo关键结构体

1、所有回滚段都记录在trx_sys->rseg_array,数组大小为128,分别对应不同的回滚段。
2、rseg_array数组类型为trx_rseg_t,用于维护回滚段相关信息。
3、每个回滚段对象trx_rseg_t还要管理undo log信息,对应结构体为trx_undo_t,使用多个链表来维护trx_undo_t信息。
4、事务开启时,会专门给他指定一个回滚段,以后该事务用到的undo log页,就从该回滚段上分配。
5、事务提交后,需要purge的回滚段会被放到purge队列上(purge_sys->purge_queue)。

快照读(普通 select 语句)是通过MVCC(多版本控制)和undo log来解决幻读。

当前读(select ... for update 等语句)是通过加record lock(记录锁)和gap lock(间隙锁)来解决幻读。

分布式系统理论

1、CAP(Consistency、Availability、Partition tolerance)

一致性(Consistency):   数据更新是一致的,所有数据节点的变动都是同步的,同时发生,同时生效。
可用性(Availability):      性能好+可靠性,集群中一部分节点故障后,集群整体还能响应客户端的请求。
分区容错性(Partition tolerance):  系统可跨网络分区线性的伸缩和扩展, 一个分布式系统里面,节点组成的网络应是连通的。因故障,有些节点之间不连通了,整个网络分成几块区域。数据就散布在这些不连通的区域中。
当一个数据项只在一个节点中保存,那么分区出现后,和这个节点不连通的部分就访问不到这个数据了。(分区容错性差)
数据项复制到多个节点上,出现分区之后,数据项就分到各个区里。把数据复制到多个节点,可能数据是不一致。(分区容错性好,一致性就差)
保证一致,每次写操作就要等节点全写成功,又会带来可用性的问题。(分区容错性好,一致性好,可用性就差)。任何分布式系统只可同时满足两点,无法三者兼顾。

2、BASE

Basically Available(基本业务可用性(支持分区失败)),Soft state(软状态,状态允许有短时间不同步,异步),Eventuallyconsistent(最终一致性(最终数据是一致的,但不是实时一致))
无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。

分布式事务解决方案

1、两阶段提交(2PC)

优点:  尽量保证了数据的强一致,适合对数据强一致要求很高的关键领域。
缺点:  实现复杂,牺牲了可用性(牺牲一部分可用性来换取的一致性),对性能影响较大,不适合高并发高性能场景。

2、补偿事务(TCC)

优点:  跟2PC比起来,实现以及流程相对简单,数据的一致性比2PC要差。
缺点:  在事务发起方远程调用事务参与方的confirm、cancel方法中都有可能失败。TCC属于应用层的一种补偿方式,实现的时多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不好定义及处理。

3、本地消息表(异步确保)

遵循BASE理论,采用最终一致性。使用独立消息服务 + MQ实现最终消息一致性(业界使用最多)。不会出现复杂的实现(当调用链很长的时候,2PC的可用性是非常低的),也不会像TCC那样可能出现确认或者回滚不了的情况。
优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。
缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,有很多杂活要处理。

刚性事务:     满足 ACID 理论,工作在数据资源层,也就是比如数据库,MQ中间件。以 xa 2pc为代表。
柔性事务:     满足 BASE 理论,数据最终一致。工作在数据业务层,如应用服务、代码层面上。TCC补偿型为代表。

主从复制 

1、原理

通过3个线程实现同步操作:
主库线程:           主服务器将数据的更新记录到二进制日志中(记录被称作二进制日志事件)。
从库I/O线程:      从库将主库的二进制日志复制到本地的中继日志(relay log)。
从库SQL线程:    从库读取中继日志中的事件,将更新到数据中。

2、配置主库

1).创建用户

建新用户用于从库连接主库
    # 创建用户 
    create user 'repl'@'%' identified by 'repl'; 
    # 授权,只授予复制和客户端访问权限 
    grant replication slave,replication client on *.* to 'repl'@'%' identified by 'repl';

2).修改配置文件

vim /etc/my.cnf 在[mysqld]下添加:
    log-bin         = mysql-bin 
    log-bin-index   = mysql-bin.index 
    binlog_format   = mixed 
    server-id       = 21 
    sync-binlog     = 1 
    character-set-server = utf8
    配置文件说明
    log-bin: 设置二进制日志文件的基本名; 
    log-bin-index: 设置二进制日志索引文件名; 
    binlog_format: 控制二进制日志格式,进而控制了复制类型,三个可选值 
        -STATEMENT: 语句复制 
        -ROW: 行复制 
        -MIXED: 混和复制,默认选项 
    server-id: 服务器设置唯一ID,默认为1,推荐取IP最后部分; 
    sync-binlog: 默认为0,为保证不会丢失数据,需设置为1,用于强制每次提交事务时,同步二进制日志到磁盘上。 
保存文件并重启主库:
    service mysqld restart 

3).备份主数据库数据

若主库可停机,则直接拷贝所有数据库文件。
若主库是在线生产库,可采用 mysqldump 备份数据,它对所有存储引擎均可使用。
    1、为了获取一个一致性的快照,需对所有表设置读锁:
    flush tables with read lock; 
    2、获取二进制日志的坐标:
    show master status; 
    3、备份数据:
    # 针对事务性引擎 
    mysqldump -uroot -ptiger --all-database -e --single-transaction --flush-logs --max_allowed_packet=1048576 --net_buffer_length=16384 > /data/all_db.sql 
    # 针对 MyISAM 引擎,或多引擎混合的数据库 
    mysqldump -uroot --all-database -e -l --flush-logs --max_allowed_packet=1048576 --net_buffer_length=16384 > /data/all_db.sql 
    4、恢复主库的写操作:
    unlock tables;

3、配置从库

1.修改配置文件

   1、vim /etc/my.cnf 在[mysqld]下添加:
        log-bin             = mysql-bin
        binlog_format       = mixed
        log-slave-updates   = 0
        server-id           = 22
        relay-log           = mysql-relay-bin
        relay-log-index     = mysql-relay-bin.index
        read-only           = 1
        slave_net_timeout   = 10
        log-slave-updates:控制 slave 上的更新是否写入二进制日志,默认为0;若 slave 只作为从服务器,则不必启用;若 slave 作为其他服务器的 master,则需启用,启用时需和 log-bin、binlog-format 一起使用,这样 slave 从主库读取日志并重做,然后记录到自己的二进制日志中;
        relay-log:设置中继日志文件基本名;
        relay-log-index:设置中继日志索引文件名;
        read-only:设置 slave 为只读,但具有super权限的用户仍然可写;
        slave_net_timeout:设置网络超时时间,即多长时间测试一下主从是否连接,默认为3600秒,即1小时,这个值在生产环境过大,我们将其修改为10秒,即若主从中断10秒,则触发重新连接动作。
    2、保存文件并重启从库
        service mysqld restart

2.导入备份数据

    如果主库没备份,忽略此步骤。
    mysql -uroot -p < /data/all_db.sql

3.统一二进制日志的坐标

    主库统一到从库中:
    change master to
    master_host='192.168.2.21',
    master_user='repl',
    master_password='repl',
    master_port=3306,
    master_log_file='mysql-bin.000001',
    master_log_pos=120;

4.启动主从复制

    1、启动从库 slave 线程:
    start slave;
    2、查看从服务器复制功能状态:
    show slave status\G;

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值