DotNetNext / SqlSugar

.Net aot ORM Fastest ORM Simple Easy VB.NET Sqlite orm Oracle ORM Mysql Orm 虚谷数据库 postgresql ORm SqlServer oRm 达梦 ORM 人大金仓 ORM 神通ORM C# ORM , C# ORM .NET ORM NET5 ORM .NET6 ORM ClickHouse orm QuestDb ,TDengine ORM,OceanBase orm,GaussDB orm ,Tidb orm Object/Relational Mapping
https://www.donet5.com/Home/Doc
MIT License
5.35k stars 1.34k forks source link

SqlSugar 并发查询的问题 #931

Closed easy999000 closed 3 years ago

easy999000 commented 3 years ago
关于SqlSugar并发查询之前在群里已经提到过几次,但是并没有引起官方足够的重视. 这次呢附上测试代码详细说明一下.不论官方是否接受这个提议这都是最后一次了.

我的主要问题是,SqlSugar对并发查询的处理不一致. 要么都考虑到并发查询,要么都不做并发检测.两种做法都没问题,无非就是把并发的检测放到框架里面,或者扔给开发者而已.  但是现在呢. 有的入口检测了并发,有的没检测,这会给开发者误导.在大量的代码中不做并发处理,但是一些从别的入口进行的查询就会报错.
 我了解到的.SqlSugar发起查询可以有两个入口 ,SqlSugarClient和SqlSugarProvider.
SqlSugarClient作为最常用的入口,是做了并发检测的, 用户使用SqlSugarClient发起查询,不论是tread还是task都不会有问题.因为在SqlSugarClient内部封装了对多线程并发的检测.
但是在一些情况下,SqlSugarClient支持内部继承多个数据库的连接.我们要对特定数据库进行查询操作的时候,就需要用到.SqlSugarClient.GetConnection()获取指定的数据库 SqlSugarProvider 对象. 然后执行操作.  SqlSugarClient和SqlSugarProvider是继承于同一个接口.查询是完全一致的.以至于开发者不会发觉有任何的不同,但是  SqlSugarProvider的并发操作是会报异常的, 
开发者习惯了,不去考虑并发的问题,因为一直都不报错,正常使用,那么在这里就会经常不小心的入坑.

所以我的建议是,在 SqlSugarProvider内部做并发查询的检测和兼容.因为我看了一下源码发现 SqlSugarClient其实就是再次调用SqlSugarProvider的方法,只不过是,给每个 线程创建了一个 SqlSugarProvider而已,  那么如果把并发的兼容功能做在 SqlSugarProvider里面, 整个SqlSugar岂不是完美兼容到了并发操作,这对  SqlSugar框架也是很大的优化.

或者干脆不做并发的检测,SqlSugarClient里面也不要做,并发就报错,这样开发者经常遇到报错,他自己就会在代码里面处理和考虑到这个问题,也不至于,项目会发生一些意料之外,难以排查的错误.  总之就是要给使用者,一致的规范,不然会产生误导.

希望官方能采纳.

下面是测试代码.

`

    public static int GetSourceCount()
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        ///这个是为了测试 GetConnection获取的对象  SqlSugarProvider 的多线程并发查询,推测应该报错
        ///结果和预期一样报异常:1: Connection open error  2::“ The ReadAsync method cannot be called when another read operation is pending
        var db = GetInstance2();
        SqlSugarProvider Provider0 = db.GetConnection("b1");
        Provider0.Ado.CommandTimeOut *= 10;

        var t1 = Task.Run<int>(() => {
            ///这个查询耗时大概80s
            Debug.WriteLine($"GetSourceCount 线程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
            var v1 = Provider0.Queryable<GoodsStockSource>().Count();
            return v1;
        });
        var t3 = Task.Run<int>(() => {
            ///这个查询耗时大概80s
            Debug.WriteLine($"GetSourceCount 线程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
            var v1 = Provider0.Queryable<GoodsStockSource>().Count();
            return v1;
        });
        var t4 = Task.Run<int>(() => {
            ///这个查询耗时大概80s
            Debug.WriteLine($"GetSourceCount 线程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
            var v1 = Provider0.Queryable<GoodsStockSource>().Count();
            return v1;
        });

        ///这个查询耗时大概80s
        Debug.WriteLine($"GetSourceCount 线程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
        var v2 = Provider0.Queryable<GoodsStockSource>().Count();

        var c1 = t1.Result;
        var c3 = t3.Result;
        var c4 = t4.Result;

        sw.Stop();
        Debug.WriteLine($"Stopwatch:{sw.Elapsed.ToString()} ");
        return c1;
    }

    public static int GetSourceCount2()
    {
        ///这个是为了测试 SqlSugarClient的多线程并发查询,推测应该正常执行
        ///结果和预期一样,正常执行

        SqlSugarClient db0 = GetInstance2();
        db0.Ado.CommandTimeOut *= 10;

        var t1 = Task.Run<int>(() => {
            ///这个查询耗时大概80s
            Debug.WriteLine($"GetSourceCount2 线程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
            var v1 = db0.Queryable<GoodsStockSource>().Count();
            return v1;
        });
        var t3 = Task.Run<int>(() => {
            ///这个查询耗时大概80s
            Debug.WriteLine($"GetSourceCount2 线程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
            var v1 = db0.Queryable<GoodsStockSource>().Count();
            return v1;
        });

        ///这个查询耗时大概80s
        Debug.WriteLine($"GetSourceCount2 线程:{System.Threading.Thread.CurrentThread.ManagedThreadId}");
        var v2 = db0.Queryable<GoodsStockSource>().Count();

        var c1 = t1.Result;
        var c3 = t3.Result;
        return c1;
    }`
DotNetNext commented 3 years ago

把new db扔到task.run里面

DotNetNext commented 3 years ago

像EF DAPPER都不支持你这种写法

easy999000 commented 3 years ago

把new db扔到task.run里面

我当然知道放到task.run里面就不报错了,这块代码我已经了解过了, 我要说明的是. 会有很多人不经意的就像我这么写.这只是为了举例说明,框架并发的测试demo而已. 如果你们不认为这个会给使用者产生一些误导,就无所谓了.

DotNetNext commented 3 years ago

你可以用sqlsugar.ioc功能 https://www.donet5.com/Home/Doc?typeId=2247 这个封装支持了线程安全只要你 DbScoped.Sugar.XXX

DotNetNext commented 3 years ago

原生的 sqlconnection 你这么用也会报错,毕竟是你的使用问题,不过sqlsugar.ioc封装会话和线程安全

easy999000 commented 3 years ago

原生的 sqlconnection 你这么用也会报错,毕竟是你的使用问题,不过sqlsugar.ioc封装会话和线程安全

sqlconnection是底层方法,sqlsugar毕竟是经过一层封装的框架,再说了, 不支持多线程并没有问题,但是SqlSugarClient为啥有支持了多线程.这不是很矛盾.

HeLinSpace commented 3 years ago

原生的 sqlconnection 你这么用也会报错,毕竟是你的使用问题,不过sqlsugar.ioc封装会话和线程安全

sqlconnection是底层方法,sqlsugar毕竟是经过一层封装的框架,再说了, 不支持多线程并没有问题,但是SqlSugarClient为啥有支持了多线程.这不是很矛盾.

请教一下,多线程为什么会使用跨线程实例对象

easy999000 commented 3 years ago

原生的 sqlconnection 你这么用也会报错,毕竟是你的使用问题,不过sqlsugar.ioc封装会话和线程安全

sqlconnection是底层方法,sqlsugar毕竟是经过一层封装的框架,再说了, 不支持多线程并没有问题,但是SqlSugarClient为啥有支持了多线程.这不是很矛盾.

请教一下,多线程为什么会使用跨线程实例对象

首先请你认真的看我上面写的代码案例, 这是一个很容易发生的代码书写方式. 全部代码都是写在一个方法里面, 很多对象都会直接用在 Lambda 方法里面. 大部分实例对象是可以这么用的. 其次 SqlSugarClient是使用最多的主要的查询方法,95%的查询入口在这里. 这个类是做了并发处理的,也就是当使用者用习惯了之后,会发现,这个类型是兼容并发操作的,并发去使用一个SqlSugarClient实例对象,并不会报错.

然后在其他特殊的一些方法里面,就会忽视这个线程并发的问题,

但是这个时候,需要用到 SqlSugarProvider .就会偶尔发生一些奇怪的异常.

HeLinSpace commented 3 years ago

原生的 sqlconnection 你这么用也会报错,毕竟是你的使用问题,不过sqlsugar.ioc封装会话和线程安全

sqlconnection是底层方法,sqlsugar毕竟是经过一层封装的框架,再说了, 不支持多线程并没有问题,但是SqlSugarClient为啥有支持了多线程.这不是很矛盾.

请教一下,多线程为什么会使用跨线程实例对象

首先请你认真的看我上面写的代码案例, 这是一个很容易发生的代码书写方式. 全部代码都是写在一个方法里面, 很多对象都会直接用在 Lambda 方法里面. 大部分实例对象是可以这么用的. 其次 SqlSugarClient是使用最多的主要的查询方法,95%的查询入口在这里. 这个类是做了并发处理的,也就是当使用者用习惯了之后,会发现,这个类型是兼容并发操作的,并发去使用一个SqlSugarClient实例对象,并不会报错.

然后在其他特殊的一些方法里面,就会忽视这个线程并发的问题,

但是这个时候,需要用到 SqlSugarProvider .就会偶尔发生一些奇怪的异常.

受教了,对了,代码全看了,不至于那么不尊重别人的劳动成果

easy999000 commented 3 years ago

原生的 sqlconnection 你这么用也会报错,毕竟是你的使用问题,不过sqlsugar.ioc封装会话和线程安全

sqlconnection是底层方法,sqlsugar毕竟是经过一层封装的框架,再说了, 不支持多线程并没有问题,但是SqlSugarClient为啥有支持了多线程.这不是很矛盾.

请教一下,多线程为什么会使用跨线程实例对象

首先请你认真的看我上面写的代码案例, 这是一个很容易发生的代码书写方式. 全部代码都是写在一个方法里面, 很多对象都会直接用在 Lambda 方法里面. 大部分实例对象是可以这么用的. 其次 SqlSugarClient是使用最多的主要的查询方法,95%的查询入口在这里. 这个类是做了并发处理的,也就是当使用者用习惯了之后,会发现,这个类型是兼容并发操作的,并发去使用一个SqlSugarClient实例对象,并不会报错. 然后在其他特殊的一些方法里面,就会忽视这个线程并发的问题, 但是这个时候,需要用到 SqlSugarProvider .就会偶尔发生一些奇怪的异常.

受教了,对了,代码全看了,不至于那么不尊重别人的劳动成果

发现问题,提出问题, 是不尊重别人的劳动成果吗?

发现了问题,提出问题,并把问题写的清清楚楚,还写了测试代码,帮助开发者完善产品,这不是雷锋吗?

我这是免费帮助他们完善产品.

HeLinSpace commented 3 years ago

原生的 sqlconnection 你这么用也会报错,毕竟是你的使用问题,不过sqlsugar.ioc封装会话和线程安全

sqlconnection是底层方法,sqlsugar毕竟是经过一层封装的框架,再说了, 不支持多线程并没有问题,但是SqlSugarClient为啥有支持了多线程.这不是很矛盾.

请教一下,多线程为什么会使用跨线程实例对象

首先请你认真的看我上面写的代码案例, 这是一个很容易发生的代码书写方式. 全部代码都是写在一个方法里面, 很多对象都会直接用在 Lambda 方法里面. 大部分实例对象是可以这么用的. 其次 SqlSugarClient是使用最多的主要的查询方法,95%的查询入口在这里. 这个类是做了并发处理的,也就是当使用者用习惯了之后,会发现,这个类型是兼容并发操作的,并发去使用一个SqlSugarClient实例对象,并不会报错. 然后在其他特殊的一些方法里面,就会忽视这个线程并发的问题, 但是这个时候,需要用到 SqlSugarProvider .就会偶尔发生一些奇怪的异常.

受教了,对了,代码全看了,不至于那么不尊重别人的劳动成果

发现问题,提出问题, 是不尊重别人的劳动成果吗?

发现了问题,提出问题,并把问题写的清清楚楚,还写了测试代码,帮助开发者完善产品,这不是雷锋吗?

我这是免费帮助他们完善产品.

我说的是我自己 image 对了 感谢雷锋帮我们这帮菜鸡踩坑

DotNetNext commented 3 years ago

没什么可以争,下个版本将支持单例