spring 事务详解

事务特性(ACID)

  • 原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用
  • 一致性: 执行事务前后,数据保持一致
  • 隔离性: 并发访问数据库时,一个用户的事物不被其他事务所干扰也就是说多个事务并发执行时,一个事务的执行不应影响其他事务的执行
  • 持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响

spring 事务管理接口介绍

Spring 框架中,事务管理相关最重要的 3 个接口如下:

  • PlatformTransactionManager: (平台)事务管理器,Spring 事务策略的核心
  • TransactionDefinition: 事务属性(事务隔离级别、传播行为、超时、只读、回滚规则)
  • TransactionStatus: 事务运行状态

我们可以把 PlatformTransactionManager 接口可以被看作是事务上层的管理者,而 TransactionDefinitionTransactionStatus 这两个接口可以看作是事务的描述。

PlatformTransactionManager 会根据 TransactionDefinition 的定义(比如事务超时时间、隔离界别、传播行为等)来进行事务管理 ,而 TransactionStatus 接口则提供了一些方法来获取事务相应的状态(比如是否新事务、是否可以回滚)

一、PlatformTransactionManager(事务管理)

spring 事务管理接口,通过这个接口,Spring 为各个平台如 JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager)等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了

PlatformTransactionManager 接口中定义了三个方法:

public interface PlatformTransactionManager {
    //获得事务
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
    //提交事务
    void commit(TransactionStatus var1) throws TransactionException;
    //回滚事务
    void rollback(TransactionStatus var1) throws TransactionException;
}

二、TransactionDefinition(事务属性)

用于描述事务隔离级别、传播行为、超时、只读、回滚规则

public interface TransactionDefinition {
    int PROPAGATION_REQUIRED = 0;
    int PROPAGATION_SUPPORTS = 1;
    int PROPAGATION_MANDATORY = 2;
    int PROPAGATION_REQUIRES_NEW = 3;
    int PROPAGATION_NOT_SUPPORTED = 4;
    int PROPAGATION_NEVER = 5;
    int PROPAGATION_NESTED = 6;
    int ISOLATION_DEFAULT = -1;
    int ISOLATION_READ_UNCOMMITTED = 1;
    int ISOLATION_READ_COMMITTED = 2;
    int ISOLATION_REPEATABLE_READ = 4;
    int ISOLATION_SERIALIZABLE = 8;
    int TIMEOUT_DEFAULT = -1;
    // 返回事务的传播行为,默认值为 REQUIRED。
    int getPropagationBehavior();
    //返回事务的隔离级别,默认值是 DEFAULT
    int getIsolationLevel();
    // 返回事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
    int getTimeout();
    // 返回是否为只读事务,默认值为 false
    boolean isReadOnly();

    @Nullable
    String getName();
}

三、TransactionStatus(事务状态)

TransactionStatus 接口用来记录事务的状态,该接口定义了一组方法用来获取或判断事务的相应状态信息

PlatformTransactionManager.getTransaction(…) 方法返回一个 TransactionStatus 对象

TransactionStatus 接口接口内容如下:

public interface TransactionStatus{
    // 是否是新的事物
    boolean isNewTransaction(); 
    // 是否有恢复点
    boolean hasSavepoint(); 
    // 设置为只回滚
    void setRollbackOnly();  
    // 是否为只回滚
    boolean isRollbackOnly(); 
    // 是否已完成
    boolean isCompleted; 
}

spring 事务属性

事务传播行为

一、简介

事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。

事务传播行为是为了解决业务层方法之间互相调用的事务问题

spring 定义了 7 中事务传播行为,其含义如下:

事务行为 说明
PROPAGATION_REQUIRED 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务
PROPAGATION_SUPPORTS 当前方法不需要事务上下文,但若存在当前事务,则该方法会加入当前事务
PROPAGATION_MANDATORY 该方法必须在事务中运行,若当前事物不存在,则抛出一个异常:IllegalTransactionStateException("Transaction propagation ‘mandatory’ but no existing transaction found")
PROPAGATION_REQUIRES_NEW 当前方法必须运行在它自己的事务中。一个新的事务将被启动。若存在当前事务,则该方法执行期间,当前事务挂起。若用 JTATransactionManager 的话,则需访问 TransactionManager。内层事务和外层事务相互独立,互不影响
PROPAGATION_NOT_SUPPORTED 该方法以非事务方式运行。若存在当前事务,则在该方法运行期间,当前事务挂起。若用 JTATransactionManager 的话,则需访问 TransactionManager
PROPAGATION_NEVER 该方法不应该运行在事务上下文,若当前有一个事务正在运行,则抛出异常
PROPAGATION_NESTED 若当前已存在一个事务,则该方法将会在嵌套事务中运行。若没有活动事务, 则按 PROPAGATION_REQUIRED 执行。嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。嵌套事务开始执行时, 它将取得一个 savepoint。若嵌套事务失败, 则回滚到此 savepoint。嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交

二、示例设计

示例设计中,我们主要分为两部分:同一类中事务方法的传播行为以及不同类中事务方法的传播行为。我们以表中的情况进行组合,模拟事务方法的传播行为

function a(外层事务方法) function b(内层事务方法)
外层事务正常执行,不捕获内层事务异常 内层事务正常执行
外层事务正常执行,捕获内层事务异常 内层事务执行异常,捕获异常
外层事务执行异常,抛出异常 内层事务执行异常,抛出异常
外层事务执行异常,捕获异常 内层非事务

2.1 同一类中事务方法的传播行

2.2 不同类中事务方法的传播行为

2.2.1 内层方法没有声明事务

场景:外层 PROPAGATION_REQUIRED,内层没有事务

结果:正常执行

@Transactional(rollbackFor = Exception.class)
public void function() {
    // todo database operate
    serviceB.innerFunction();
}

public void innerFunction() {
    // todo database operate
}

场景:外层 PROPAGATION_REQUIRED,内层抛出异常,外层没有捕获异常

结果:全部回滚

@Transactional(rollbackFor = Exception.class)
public void function() {
    // todo database operate
    serviceB.innerFunction();
}

public void innerFunction() {
    // todo database operate
    throw new RuntimeException("innerFunction RuntimeException");
}

场景:外层 PROPAGATION_REQUIRED,内层抛出异常,外层捕获异常

结果:正常执行,不回滚

@Transactional(rollbackFor = Exception.class)
    public void function() {
        // todo database operate
        try {
            serviceB.innerFunction();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

public void innerFunction() {
    // todo database operate
    throw new RuntimeException("innerFunction RuntimeException");
}
2.2.2 PROPAGATION_REQUIRED

使用的最多的一个事务传播行为,我们平时经常使用的 @Transactional 注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。也就是说:

  1. 如果外部方法没有开启事务的话,Propagation.REQUIRED 修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰
  2. 如果外部方法开启事务并且被 Propagation.REQUIRED 的话,所有 Propagation.REQUIRED 修饰的内部方法和外部方法均属于同一事务 ,只要一个方法回滚,整个事务均回滚

场景:内层 PROPAGATION_REQUIRED,并抛出异常,外层事务不捕获异常

结果:全部回滚

原因:外层事务方法没有捕获内层事务方法抛出的异常,因此进行回滚操作

@Transactional(rollbackFor = Exception.class)
public void function() {
    // todo database operate
    serviceB.innerFunction();
}

@Transactional(rollbackFor = Exception.class)
public void innerFunction() {
    // todo database operate
    throw new RuntimeException("innerFunction RuntimeException");
}

场景:内层 PROPAGATION_REQUIRED,并抛出异常,外层事务捕获异常

结果:全部回滚

原因:当内层事务异常的情况下,如果是 PROPAGATION_REQUIRED,正常来讲是需要回滚的,但是 spring 只给内层事务做了一个 rollback 的标记,当内层事务抛出的异常被外层捕获时,外层事务正常执行,但在最后提交的时候发现,内层事务被标记为 rollbck,所以就会抛出 UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

@Transactional(rollbackFor = Exception.class)
public void function() {
    // todo database operate
    try {
        serviceB.innerFunction();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

@Transactional(rollbackFor = Exception.class)
public void innerFunction() {
    // todo database operate
    throw new RuntimeException("innerFunction RuntimeException");
}

解决 UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only 的方案有两个:

  1. 内层事务方法捕获自己抛出的异常

    @Transactional(rollbackFor = Exception.class)
    public void innerFunction() {
        try {
            // todo database operate
            throw new RuntimeException("innerFunction RuntimeException");
        } catch (RuntimeException e) {
            e.printStackTrace();
        }
    }
    
  2. 将内层事务传播行为改为 PROPAGATION_REQUIRES_NEW,详情见 2.2.3 Propagation.REQUIRES_NEW

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void innerFunction() {
        // todo database operate
        throw new RuntimeException("innerFunction RuntimeException");
    }
    
2.2.3 PROPAGATION_REQUIRES_NEW

创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰

场景:内层 PROPAGATION_REQUIRES_NEW,并抛出异常,外层事务不捕获异常

结果:外层事务不回滚,内层事务回滚

原因:内外层事务互不影响,内层事务的回滚不影响外层事务的正常执行

@Transactional(rollbackFor = Exception.class)
public void function() {
    // todo database operate
    serviceB.innerFunction();
}

@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void innerFunction() {
    // todo database operate
    throw new RuntimeException("innerFunction RuntimeException");
}

场景:内层 PROPAGATION_REQUIRES_NEW,并抛出异常,外层事务捕获异常

结果:外层事务不回滚,内层事务回滚

原因:内外层事务互不影响,内层事务的回滚不影响外层事务的正常执行

@Transactional(rollbackFor = Exception.class)
public void function() {
    // todo database operate
    try {
        serviceB.innerFunction();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void innerFunction() {
    // todo database operate
    throw new RuntimeException("innerFunction RuntimeException");
}

场景:外层事务抛出异常,内层事务正常执行

结果:外层事务回滚,内层事务不回滚

原因:内外层事务互不影响,外层事务的回滚不影响内层事务的正常执行

@Transactional(rollbackFor = Exception.class)
public void function() {
    // todo database operate
    serviceB.innerFunction();
    throw new RuntimeException("function RuntimeException");
}

@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void innerFunction() {
    // todo database operate
}

场景:外层事务抛出异常并捕获,内层事务正常执行

结果:内外层事务均不回滚

原因:外层事务捕获了 RuntimeException,因此不回滚

@Transactional(rollbackFor = Exception.class)
public void function() {
    // todo database operate
    try {
        serviceB.innerFunction();
        throw new RuntimeException("function RuntimeException");
    } catch (Exception e) {
        e.printStackTrace();
    }
}

@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void innerFunction() {
    // todo database operate
}
2.2.4 PROPAGATION_NESTED

如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 TransactionDefinition.PROPAGATION_REQUIRED。也就是说:

  1. 在外部方法未开启事务的情况下 Propagation.NESTED 和 Propagation.REQUIRED 作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。
  2. 如果外部方法开启事务的话,Propagation.NESTED 修饰的内部方法属于外部事务的子事务,外部主事务回滚的话,子事务也会回滚,而内部子事务可以单独回滚而不影响外部主事务和其他子事务

场景:外层事务方法 function 正常执行,内层事务方法 innerFunction1 执行正常,内存事务方法 innerFunction2 抛出异常

结果:全部回滚

原因:有人可能会问,不应该是内层事务的回滚不影响外层事务执行吗?为什么会全部回滚。原因在于 innerFunction2 抛出 RuntimeException 后,function 没有进行捕获处理,因此该 RuntimeException 出发了 function 的 rollbackFor = {Exception.class} 条件,导致所以操作均回滚。正确的方式为对 innerFunction2 包一层 try-catch 语句,这样就达到内层事务回滚不影响外层事务了

// function 没有捕获 innerFunction2 抛出的异常,因此 function 也会回滚,这是错误的打开方式
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
public void function() {
    // todo database operate
    serviceB.innerFunction1();
    serviceB.innerFunction2();
}

// 这才是正确的打开方式
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
public void function11() {
    // todo database operate
    try {
        serviceB.innerFunction1();
        serviceB.innerFunction2();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
public void innerFunction1() {
    // todo database operate
}

@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
public void innerFunction2() {
    // todo database operate
    throw new RuntimeException("innerFunction7 RuntimeException");
}

场景:外层事务方法 function 抛出异常,内层事务方法 innerFunction1 和 innerFunction2 执行正常

结果:全部回滚

原因:外层事务影响内层事务

@Transactional(propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
public void function() {
    // todo database operate
    try {
        serviceB.innerFunction1();
        serviceB.innerFunction2();
    } catch (Exception e) {
        e.printStackTrace();
    }
    throw new RuntimeException("function RuntimeException");
}

@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
public void innerFunction1() {
    // todo database operate
}

@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
public void innerFunction2() {
    // todo database operate
}
2.2.5 PROPAGATION_MANDATORY

如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)

事务隔离级别

TransactionDefinition 接口中定义了五个表示隔离级别的常量:

public interface TransactionDefinition {
    ......
    int ISOLATION_DEFAULT = -1;
    int ISOLATION_READ_UNCOMMITTED = 1;
    int ISOLATION_READ_COMMITTED = 2;
    int ISOLATION_REPEATABLE_READ = 4;
    int ISOLATION_SERIALIZABLE = 8;
    ......
}
事务隔离界别 说明
ISOLATION_DEFAULT 使用后端数据库默认的隔离级别,MySQL 默认采用的 REPEATABLE_READ 隔离级别,Oracle 默认采用的 READ_COMMITTED 隔离级别
ISOLATION_READ_UNCOMMITTED 最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
ISOLATION_READ_COMMITTED 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
ISOLATION_REPEATABLE_READ 同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
ISOLATION_SERIALIZABLE 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别

MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)。我们可以通过 SELECT @@tx_isolation; 命令来查看

这里需要注意的是:与 SQL 标准不同的地方在于 InnoDB 存储引擎在 REPEATABLE-READ(可重读) 事务隔离级别下使用的是 Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以说 InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读) 已经可以保证事务的隔离性要求,即达到了 SQL 标准的 SERIALIZABLE(可串行化) 隔离级别

事务超时属性

所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒,默认值为-1

事务只读属性

public interface TransactionDefinition {
    ......
    // 返回是否为只读事务,默认值为 false
    boolean isReadOnly();
}

对于只有读取数据查询的事务,可以指定事务类型为 readonly,即只读事务。只读事务不涉及数据的修改,数据库会提供一些优化手段,适合用在有多条数据库查询操作的方法中

很多人就会疑问了,为什么我一个数据查询操作还要启用事务支持呢?

拿 MySQL 的 innodb 举例子,根据官网 https://dev.mysql.com/doc/refman/5.7/en/innodb-autocommit-commit-rollback.html 描述:

MySQL 默认对每一个新建立的连接都启用了autocommit模式。在该模式下,每一个发送到 MySQL 服务器的sql语句都会在一个单独的事务中进行处理,执行结束后会自动提交事务,并开启一个新的事务。

但是,如果你给方法加上了 @Transactional 注解的话,这个方法执行的所有sql会被放在一个事务中。如果声明了只读事务的话,数据库就会去优化它的执行,并不会带来其他的什么收益。

如果不加 @Transactional,每条 sql 会开启一个单独的事务,中间被其它事务改了数据,都会实时读取到最新值

分享一下关于事务只读属性,其他人的解答:

  1. 如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持 SQL 执行期间的读一致性
  2. 如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询 SQL 必须保证整体的读一致性,否则,在前条 SQL 查询之后,后条 SQL 查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持

事务回滚规则

默认情况下,事务只有遇到运行期异常(RuntimeException 的子类)时才会回滚,Error 也会导致事务回滚,但是,在遇到检查型(Checked)异常时不会回滚

事务失效场景

一、数据库引擎是否支持事务

Mysql 的 MyIsam 引擎不支持事务

二、注解所在的类是否注入 spring 容器中

三、注解所在方法不是 public 或者是 final

这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。

在 spring 中动态代理分为 JDK 动态代理和 CGLIB 动态代理,JDK 动态代理要求必须实现接口(所以方法必须是public的),但是 CGLIB 动态代理底层则是通过字节码生成被代理类的子类来实现的,这里要求被代理类必须能被继承(public 和 protected),被 final 修饰的方法不能被子类继承,因此 @Transactional 注解无效。但为何 @Transactional 注解不支持 protected 方法呢?

spring 官方文档中有如下说明:

Spring AOP 对 privateprotect 是不支持的,无论是 JDK 还是 CGLIB,如果要对 protect 方法进行拦截,建议使用 AspectJ

不清楚 Spring 为什么不推荐其 AOP 对 protect 不支持,猜测可能:

  1. 代理行为本身就是一种三方调用的思想,那么被代理的方法本身应该是公有的
  2. 为了跟让 CGLIB 和 JDK 保持一致,因为 JDK 基于接口的肯定都是 public 的,而 CGLIB 干嘛搞特殊?
  3. 待续猜想

四、所用数据源是否加载了事务管理器

五、事务自调用(同一个类中的 A 方法调用 B 方法)

若同一类中的其他没有 @Transactional 注解的方法内部调用有 @Transactional 注解的方法,有 @Transactional 注解的方法的事务被忽略,不会发生回滚

使用 AOP 代理后的方法调用执行流程,如图所示,可以看到调用者首先调用的是 AOP 代理对象而不是目标对象,首先执行事务切面,事务切面内部通过 TransactionInterceptor 环绕增强进行事务的增强,即进入目标方法之前开启事务,退出目标方法时提交/回滚事务

目标对象内部的自我调用将无法实施切面中的增强,如图所示,this 指向目标对象,因此调用 this.b() 将不会执行 b 事务切面,即不会执行事务增强

六、当方法发生异常时,使用 try-catch 捕获了异常,并且 catch 中没有抛出异常或者手动回滚

事务的回滚是方法发生异常,在 aop 的异常通知中进行拦截回滚。如果方法中捕获了异常,是不会被 aop 的异常通知拦截到的。如果使用 try-catch 捕获异常,需要在catch中抛出一个异常或者在 catch 中通过 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() 设置手动回滚

@Transactional 事务注解原理

@Transactional 的工作机制是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果目标对象没有实现接口,会使用 CGLIB 动态代理

createAopProxy 方法 决定了是使用 JDK 还是 Cglib 来做动态代理,源码如下:

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

    @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
            ...
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
            return new ObjenesisCglibAopProxy(config);
        } else {
            return new JdkDynamicAopProxy(config);
        }
    }
    .......
}

如果一个类或者一个类中的 public 方法上被标注 @Transactional 注解的话,Spring 容器就会在启动的时候为其创建一个代理类,在调用被 @Transactional 注解的 public 方法的时候,实际调用的是,TransactionInterceptor 类中的 invoke 方法。这个方法的作用就是在目标方法之前开启事务,方法执行过程中如果遇到异常的时候回滚事务,方法调用完成之后提交事务

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,076评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,658评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,732评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,493评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,591评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,598评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,601评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,348评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,797评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,114评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,278评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,953评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,585评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,202评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,180评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,139评论 2 352