数据库系统开发是现今软件开发的主要领域。对数据库系统开饭数据库模式(包括数据库反模式)早就有人探讨过了,也出了很多书。它们主要针对的是数据库的规则、约束、架构、存储、性能等等的研究。我们所说的数据库驱动开发定义完全不同。它的定义是:如果一个软件用不用到数据库,我们却要指在软件设计编码之前,先在数据库中凭直觉创建需要的东西。然后对此编码实验测试,看看行不行,如果可行,就可以继续原来的步骤了。
数据驱动开发的步骤是:编写代码,编写代码之前编写测试代码,在编写测试代码之前灵活设计数据库及原型。有史为证:我劝天公重抖擞,不拘一格降数据库。
主要应用场景是系统API、行业系统、互联网、物联网、游戏开发、云、大数据、人工智能等等。它的使用约定为:在编写代码之前,先根据业务场景建表建库,在完成存储模型、数据协定、流程契约的基础上,才能按照传统的方法开始编写软件。
数据库技术目前经过很多年的发展,它的技术、算法、应用都达到了一个很高的层次,数据库领域也是目前研究比较热门的领域,新的模型、技术还在不断发展,例如网状数据库、图形数据库、文档数据库、XML数据库、面向对象数据库、内存数据库、实时数据库、NoSql 数据库、等等等等。
目前还有很多现有的软件系统因为各种原因(历史原因、架构原因、编程习惯、实现难度、思维习惯等等)在开发过程中没有使用数据库驱动开发或使用的程度不够,这不能不说是一大憾事。后面我们专门挑选了一些在传统上属于非数据库驱动开发的编程场景在这里加以改造,这也是本书要讲述的内容重点。
当然,数据库驱动开发也不是万精油,存在一些场景,我们的数据库引擎、接口、数据存储、日志、锁、隔离级别等等可能不能满足应用要求,或者由于语言、性能、空间、速度、数据格式等等问题,有可能需要更换数据库引擎,修改数据库的源码,使用数据库模式构造一个假的可验证原型软件,或干脆放弃数它。
1 处理器调度
处理器调度是操作系统的基础功能。
1、常见调度算法:
1)先来先服务(FirstComeFirstService,FCFS)。
2)最短作业优先(Shortest Job First,SJF)。
3)时间片时钟(RoundRobin)。
4)多级队列 (Multiple-levelQueue)。
5)优先级 (PriorityScheduling)。
6)多级反馈队列(RoundRobin withMultiple Feedback)。
2、调度算法的选择规则:
1)面向用户:周转时间短,响应时间快,截止时间的保证,优先权准则。
2)面向系统:系统吞吐量高,处理机利用率好,各类资源的平衡利用。
3、采用数据库模式的演示系统只是实现了最简单的优先级规则。处理器调度包括单处理器、对称多处理器(SMP)、多计算机调度等等。我们先实现一个最简单的单处理器例子,然后是对称多处理器,多计算机调度的实现例子。
1.1 单处理器调度(94-11)
这是一个关于单核、单进程处理器调度的例子,这里假设以下条件:处理器为单核,每个进程有且只有一个线程。叫做老式的进程调度。它不用考虑资源锁定问题,使得问题得到了化简。
基本算法是:
1)创建一张进程表,包括编号、状态、优先级三列,状态仅包括就绪和执行两种状态。输入几条初始化数据,优先级默认为0,编号从10开始累加。
2)程序是一个循环,首先显示文字菜单。选择运行则执行调度代码,选择查看则会列表显示、编辑、删除进程表的数据,选择退出就会退出程序。
3)如果选择执行,首先增加就绪队列的优先级(20),然后降低执行进程的优先级(-20)并将状态改为就绪,然后在全部队列查找优先级最高的进程予以运行。如果找到了多个,仅修改第1个。
程序开发运行环境: DOS7.1,Foxbase2.1 。
- 程序运行
1)进入Foxbase。
2)建立数据表 Process 结构见下:
| Name | Type | Size | Min | 备注 |
| PROCESSID | CHARACTER | 32 | 进程编号 | |
| STATUS | CHARACTER | 1 | 状态(R:Ready, E:Executing) | |
| PRIORITY | NUMERIC | 6 | 优先级 |
3)然后录入四条样本数据见下。
| PROCESSID | STATUS | PRIORITY |
| 10 | R | 0 |
| 20 | R | 0 |
| 30 | R | 0 |
| 40 | R | 0 |
4)执行 do process 。运行结果见后面的截图。
- 代码
use process
do while .t.
clear
?
? “ Process Schedule Program.”
@08,5 prompt “Run: Running App.”message””
@09,5 prompt “Browser: View Process In Browser.”message””
@10,5 prompt “Quit: Quit App.”message””
menu to selCmd
set message to 23
do case
case selCmd = 1
&& 就绪队列加 20
replace all priority with priority + 20 for status =”R”
&& 执行的改为就绪,减去20
replace all status with “R”, priority with priority – 20 for status =”E”
go top
prior = -9999
&& 查找最高优先级
do while !eof()
prior = IIF(priority > prior, priority ,prior)
skip
enddo
&& 将第一个改为执行,显示在屏幕上,等待用户回车确认
locate for priority = prior
if found()
replace status with “E”
display
accept “Enter continue.” To emp
endif
case selCmd = 2
&& 显示,编辑,删除进程列表
browse
case selCmd = 3
exit
endcase
enddo
- 执行结果
程序的入口界面

程序某次调度运行情况:

显示目前的进程列表:

- 扩展阅读
1)Foxbase实际上也支持多用户及表锁,所以我们也能通过它实现多处理器调度,使用多台PC模拟多个处理器,这里就不实现了。SQL Server 在这方面更为强大,所以后面的例子将使用SQL Server实现多处理器调度。
2)按照《没有银弹》作者的说法,没有方法可以提高软件开发效率10倍。这里的程序,代码长度大约减少了10倍。
1.2 多处理器调度
本例子是有关于多核与线程调度的,它在前一个例子的基础上,在表中增加了一个 CPUId 字段,可以为空。它用来存储当前正在运行的处理器编号。这样就可以记录、跟踪线程与处理器的关系。我们使用创建新的线程来模拟多个处理器,这样可以做到比较接近真实系统的情况。
程序实现多个处理器的调度算法,使用了数据库提供的表锁,等价于实际系统中的自旋锁。由于SQL Server自己就支持表锁,所以我们就不需要自己实现,直接调用即可(with (tablockx))。我们的调度时间间隔为2400ms,自动刷新数据的间隔为600ms。
程序开发运行环境:.Net framework 4.5,visual Studio 2013,SQL Server 2008R2 。
- 程序运行
1)进入SQL企业管理器。
2)建立数据表 Thread 结构见下:
| Name | Type | Size | Default | AllowNull | PK | 备注 |
| ThreadId | Int | false | True | 线程编号 | ||
| Status | nchar(1) | 1 | False | 状态(R:Ready, E:Executing) | ||
| Priority | Int | 0 | false | 优先级 | ||
| CPUId | Int | True | 关联处理器,可为空 |
3)然后录入6 条样本数据见下
| ThreadId | Status | Priority | CPUId |
| 1 | R | 0 | Null |
| 2 | R | 0 | Null |
| 3 | R | 0 | Null |
| 4 | R | 0 | Null |
| 5 | R | 0 | Null |
| 6 | R | 0 | Null |
4)编译代码,执行 SMP.exe。运行结果见后面的截图。
- 代码
// 1)主窗体:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace SMP
{
/// <summary>
/// 主窗体,显示所有的调度线程列表
/// </summary>
public partial class FormMain : Form
{
// 数据库连接字符串,需要请在这里修改
public static string DBConn = @"Data Source=.\sqlexpress;Initial Catalog=SMP;Persist Security Info=True;User ID=sa;Password=sa";
private SqlConnection smp = new SqlConnection(DBConn);
private SqlCommand command;
SqlDataAdapter da;
/// <summary>
/// 默认的处理器计数器
/// </summary>
int cpuId =0;
/// <summary>
/// 构造
/// </summary>
public FormMain()
{
InitializeComponent();
}
/// <summary>
/// 窗体装入
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FormMain_Load(object sender, EventArgs e)
{
try
{
smp.Open();
command = smp.CreateCommand();
// 初始化线程表为就绪状态,优先级为0,无处理器附加
command.CommandText = "update Thread set Status = 'R', Priority = 0, CPUId = null";
command.ExecuteNonQuery();
// 表锁
da = new SqlDataAdapter("select * from Thread with (tablockx)", smp);
LoadData();
}
catch(Exception ex)
{
throw;
}
}
/// <summary>
/// 装入数据到网格
/// </summary>
private void LoadData()
{
DataTable dt = new DataTable();
da.Fill(dt);
threadDataGridView.DataSource = dt;
}
/// <summary>
/// 增加处理器
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void buttonNew_Click(object sender, EventArgs e)
{
FormCPU.Execute(cpuId++);
}
/// <summary>
/// 手动刷新数据
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void buttonLoad_Click(object sender, EventArgs e)
{
LoadData();
}
/// <summary>
/// 窗体关闭时,关闭其它线程的消息循环窗体
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FormMain_FormClosing(object sender, FormClosingEventArgs e)
{
timerAuto.Enabled = false;
List<Form> chilren = new List<Form>();
foreach (var qf in Application.OpenForms)
chilren.Add(qf as Form);
foreach (var qf in chilren)
{
if ((qf != this) && (qf is FormCPU))
(qf as FormCPU).Close();
}
}
/// <summary>
/// 主窗体显示数据自动刷新的切换
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void checkBoxAuto_CheckedChanged(object sender, EventArgs e)
{
timerAuto.Enabled = checkBoxAuto.Checked;
}
/// <summary>
/// 自动刷新界面,默认间隔为 600ms
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void timerAuto_Tick(object sender, EventArgs e)
{
LoadData();
}
}
}
// 处理器窗体:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace SMP
{
/// <summary>
/// 处理器窗体,每个包含一个处理器线程,和数据库连接对象,已经自己的 CPUId。
/// </summary>
public partial class FormCPU : Form
{
/// <summary>
/// 构造
/// </summary>
public FormCPU()
{
InitializeComponent();
}
/// <summary>
/// 连接对象
/// </summary>
private SqlConnection smp;
/// <summary>
/// 我的处理器编号
/// </summary>
private int cpuId;
/// <summary>
/// 支持多实例的工厂
/// </summary>
/// <param name="cpuId"></param>
public static void Execute(int cpuId)
{
Thread t = new Thread(new ParameterizedThreadStart(FormCPU.CreateThread));
t.IsBackground = false;
t.Start(cpuId);
}
/// <summary>
/// 创建处理器对象,是UI消息循环模式
/// </summary>
/// <param name="cpuId"></param>
private static void CreateThread(object cpuId)
{
FormCPU ft = new FormCPU();
ft.cpuId = (int)cpuId;
ft.Text += " - " + cpuId.ToString();
ft.smp = new SqlConnection(FormMain.DBConn);
ft.smp.Open();
Application.Run(new ApplicationContext(ft));
}
/// <summary>
/// 在处理器窗体关闭之前,如果有自己关联的线程,需要清除。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FormThread_FormClosing(object sender, FormClosingEventArgs e)
{
timerScheduling.Enabled = false;
var trans = smp.BeginTransaction();
try
{
ResetCurrent(trans);
trans.Commit();
}
catch
{
trans.Rollback();
}
smp.Close();
Application.ExitThread();
}
/// <summary>
/// 调度程序,定时器默认间隔为2400ms。锁定方式为表锁定
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void timerScheduling_Tick(object sender, EventArgs e)
{
var trans = smp.BeginTransaction();
try
{
SqlCommand command = ResetCurrent(trans);
SqlParameter qp;
// 就绪队列优先级加5
command.CommandText = "update Thread set Priority = Priority + 5 where (Status = 'R') and (CPUId is null) ";
command.Parameters.Clear();
command.ExecuteNonQuery();
// 找到就绪队列中优先级最高的线程
command.CommandText = "select top 1 threadid from Thread where Priority = (select MAX(priority) from Thread where (Status = 'R') and (CPUId is null))";
command.Parameters.Clear();
object newid = command.ExecuteScalar();
if (newid != null) // 如果找到了
{
int nid = (int)newid;
// 设置状态为执行
command.CommandText = "update Thread set Status = 'E', CPUId = @CPUId where (ThreadId = @ThreadId) ";
command.Parameters.Clear();
qp = command.CreateParameter();
qp.ParameterName = "@CPUId";
qp.Value = cpuId;
command.Parameters.Add(qp);
qp = command.CreateParameter();
qp.ParameterName = "@ThreadId";
qp.Value = nid;
command.Parameters.Add(qp);
var qret = command.ExecuteNonQuery();
labelExecute.Text = nid.ToString();
}
else
labelExecute.Text = "none";
trans.Commit();
}
catch
{
trans.Rollback();
}
}
/// <summary>
/// 复位当前执行的线程,降低优先级,状态改为就绪,当前附加处理器清空
/// </summary>
/// <param name="trans"></param>
/// <returns></returns>
private SqlCommand ResetCurrent(SqlTransaction trans)
{
var command = smp.CreateCommand();
command.Transaction = trans;
// 表锁,得到我正在执行的线程
command.CommandText = "select * from Thread with (tablockx) where (Status = 'E') and (CPUId = @CPUId) ";
SqlParameter qp = command.CreateParameter();
qp.ParameterName = "@CPUId";
qp.Value = cpuId;
command.Parameters.Add(qp);
object threadId = command.ExecuteScalar();
if (threadId != null) // 如果找到了
{
int tid = (int)threadId;
// 清除处理器关联,置状态为就绪,降低优先级24
command.CommandText = "update Thread set Status = 'R', Priority = Priority - 24, CPUId = null where (ThreadId = @ThreadId) ";
command.Parameters.Clear();
qp = command.CreateParameter();
qp.ParameterName = "@ThreadId";
qp.Value = tid;
command.Parameters.Add(qp);
var qret = command.ExecuteNonQuery();
}
return command;
}
}
}
// 复位当前执行的线程,降低优先级,状态改为就绪,当前附加处理器清空
private SqlCommand ResetCurrent(SqlTransaction trans)
{
var command = smp.CreateCommand();
command.Transaction = trans;
command.CommandText = "select * from Thread with (tablockx) where (Status = 'E') and (CPUId = @CPUId) ";
SqlParameter qp = command.CreateParameter();
qp.ParameterName = "@CPUId";
qp.Value = cpuId;
command.Parameters.Add(qp);
object threadId = command.ExecuteScalar();
if (threadId != null)
{
int tid = (int)threadId;
command.CommandText = "update Thread set Status = 'R', Priority = Priority - 24, CPUId = null where (ThreadId = @ThreadId) ";
command.Parameters.Clear();
qp = command.CreateParameter();
qp.ParameterName = "@ThreadId";
qp.Value = tid;
command.Parameters.Add(qp);
var qret = command.ExecuteNonQuery();
}
return command;
}
}
}
- 执行结果
进入程序后,选中自动刷新:

点击两次新建处理器的结果:

点击四次新建处理器的结果:
