Vda305 concurrency guerrero

62
Session Session VDA305 VDA305 Concurrency problems and Concurrency problems and locking techniques in locking techniques in SQL Server 2000 and SQL Server 2000 and VB.NET VB.NET Fernando G. Guerrero Fernando G. Guerrero SQL Server MVP SQL Server MVP .NET Technical Lead .NET Technical Lead QA plc QA plc October 2002 October 2002

description

 

Transcript of Vda305 concurrency guerrero

Page 1: Vda305 concurrency guerrero

Session Session VDA305VDA305

Concurrency problems and Concurrency problems and locking techniques in SQL locking techniques in SQL Server 2000 and VB.NETServer 2000 and VB.NET

Fernando G. GuerreroFernando G. GuerreroSQL Server MVPSQL Server MVP

.NET Technical Lead.NET Technical LeadQA plcQA plc

October 2002October 2002

Page 2: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Quick info about FernandoQuick info about Fernando(2 milliseconds)(2 milliseconds)

QA• MCSD, MCSE+Internet (W2K), MCDBA, MCT,

SQL Server MVP

• This is where I work: QA, The best learning environment in Europe

• Writing for SQL Sever Magazine and SQL Server Professional

• This is my main web site: www.callsql.com

• This is my book (so far):– Microsoft SQL Server 2000 Programming by

Example (ISBN : 0789724499, co-authored with Carlos Eduardo Rojas)

• Currently writing on ADO.NET and SQL Server 2000

Page 3: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

AgendaAgenda

• Concurrency problems

• Isolation levels

• Locks

• Transactions

33

Page 4: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Concurrency problemsConcurrency problems

• Lost Updates

• Uncommitted Dependency

• Inconsistent Analysis

• Phantom Reads

44

Page 5: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Lost Updates (1)Lost Updates (1)

Peter Paul

Mary(SQL Server)

Peter PaulUnitPrice UnitPrice

10.0 10.0

55

Page 6: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Lost Updates (2)Lost Updates (2)

Peter Paul

Mary(SQL Server)

DECLARE @UP money

SELECT @UP = UnitPriceFROM ProductsWHERE ProductID = 25

Peter PaulUnitPrice @UP *

1.2UnitPrice

10.0 12.0 10.0

66

Page 7: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Lost Updates (3)Lost Updates (3)

Peter Paul

Mary(SQL Server)

DECLARE @UP money

SELECT @UP = UnitPriceFROM ProductsWHERE ProductID = 25

DECLARE @UP money

SELECT @UP = UnitPriceFROM ProductsWHERE ProductID = 25

Peter PaulUnitPrice @UP *

1.2UnitPrice @UP *

1.1

10.0 12.0 10.0 11.0

77

Page 8: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Lost Updates (4)Lost Updates (4)

Peter Paul

Mary(SQL Server)

DECLARE @UP money

SELECT @UP = UnitPriceFROM ProductsWHERE ProductID = 25

UPDATE ProductsSET UnitPrice =

@UP * 1.2WHERE ProductID = 25

DECLARE @UP money

SELECT @UP = UnitPriceFROM ProductsWHERE ProductID = 25

Peter PaulUnitPrice @UP *

1.2UnitPrice @UP *

1.1

12.0 12.0 12.0 11.0

88

Page 9: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Lost Updates (5)Lost Updates (5)

Peter Paul

Mary(SQL Server)

DECLARE @UP money

SELECT @UP = UnitPriceFROM ProductsWHERE ProductID = 25

UPDATE ProductsSET UnitPrice = @UP * 1.2WHERE ProductID = 25

DECLARE @UP money

SELECT @UP = UnitPriceFROM ProductsWHERE ProductID = 25

UPDATE ProductsSET UnitPrice =

@UP * 1.1WHERE ProductID = 25

Peter PaulUnitPrice @UP *

1.2UnitPrice @UP *

1.1

11.0 12.0 11.0 11.0

99

Page 10: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Uncommitted Dependency (1)Uncommitted Dependency (1)

Peter Paul

Mary(SQL Server)

Peter PaulUnitPrice UnitPrice

10.0 10.0

1010

Page 11: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Uncommitted Dependency (2)Uncommitted Dependency (2)

Peter Paul

Mary(SQL Server)

BEGIN TRANSACTION

UPDATE PRODUCTSSET UnitPrice =

UnitPrice * 1.2WHERE ProductID = 25

Peter PaulUnitPrice UnitPrice

12.0 12.0

1111

Page 12: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Uncommitted Dependency (3)Uncommitted Dependency (3)

Peter Paul

Mary(SQL Server)

BEGIN TRANSACTION

UPDATE PRODUCTSSET UnitPrice = UnitPrice * 1.2WHERE ProductID = 25

DECLARE @UP money

SELECT @UP = UnitPriceFROM Products (NOLOCK)WHERE ProductID = 25

Peter PaulUnitPrice UnitPrice @UP

12.0 12.0 12.0

1212

Page 13: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Uncommitted Dependency (4)Uncommitted Dependency (4)

Peter Paul

Mary(SQL Server)

BEGIN TRANSACTION

UPDATE PRODUCTSSET UnitPrice = UnitPrice * 1.2WHERE ProductID = 25

ROLLBACK TRANSACTION

DECLARE @UP money

SELECT @UP = UnitPriceFROM ProductsWHERE ProductID = 25

Peter PaulUnitPrice UnitPrice @UP

10.0 10.0 12.0

Peter PaulUnitPrice UnitPrice @UP

12.0 12.0 12.0

1313

Page 14: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Uncommitted Dependency (5)Uncommitted Dependency (5)

Peter Paul

Mary(SQL Server)

BEGIN TRANSACTION

UPDATE PRODUCTSSET UnitPrice = UnitPrice * 1.2WHERE ProductID = 25

ROLLBACK TRANSACTION

DECLARE @UP money

SELECT @UP = UnitPriceFROM ProductsWHERE ProductID = 25

INSERT [Order details] (OrderID, ProductID, UnitPrice,Quantity, Discount)

VALUES (25365, 25,

@UP, 10, 0.1)

Peter PaulUnitPrice UnitPrice @UP

10.0 10.0 12.0

1414

Page 15: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Inconsistent Analysis (1)Inconsistent Analysis (1)

Peter Paul

Mary(SQL Server)

Peter

1515

Page 16: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Inconsistent Analysis (2)Inconsistent Analysis (2)

Peter Paul

Mary(SQL Server)

DECLARE @Count int, @Total money, @Average money

SELECT @Count = COUNT(DISTINCT

OrderID)FROM [Order Details]

Peter

@Count 830

@Total

@Average

@Total / @Count

1616

Page 17: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Inconsistent Analysis (3)Inconsistent Analysis (3)

Peter Paul

Mary(SQL Server)

DECLARE @Count int, @Total money, @Average moneySELECT @Count = COUNT(DISTINCT OrderID)FROM [Order Details]

UPDATE [Order details]SET Quantity = 600WHERE OrderID = 10272AND ProductID = 20

Peter

@Count 830

@Total

@Average

@Total / @Count

1717

Page 18: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Inconsistent Analysis (4)Inconsistent Analysis (4)

Peter Paul

Mary(SQL Server)

DECLARE @Count int, @Total money, @Average moneySELECT @Count = COUNT(DISTINCT OrderID)FROM [Order Details]

SELECT @Total = SUM(UnitPrice * Quantity * (1 – Discount))FROM [Order Details]

UPDATE [Order details]SET Quantity = 600WHERE OrderID = 10272AND ProductID = 20

Peter

@Count 830

@Total 1304284.24

@Average

@Total / @Count

1571.43

1818

Page 19: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Inconsistent Analysis (5)Inconsistent Analysis (5)

Peter Paul

Mary(SQL Server)

DECLARE @Count int, @Total money, @Average moneySELECT @Count = COUNT(DISTINCT OrderID)FROM [Order Details]

SELECT @Total = SUM(UnitPrice * Quantity * (1 – Discount))FROM [Order Details]

UPDATE [Order details]SET Quantity = 600WHERE OrderID = 10272AND ProductID = 20

UPDATE [Order details]SET Discount = 0.4WHERE ProductID = 20

Peter

@Count 830

@Total 1304284.24

@Average

@Total / @Count

1571.43

1919

Page 20: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Inconsistent Analysis (6)Inconsistent Analysis (6)

Peter Paul

Mary(SQL Server)

DECLARE @Count int, @Total money, @Average moneySELECT @Count = COUNT(DISTINCT OrderID)FROM [Order Details]

SELECT @Total = SUM(UnitPrice * Quantity * (1 – Discount))FROM [Order Details]

UPDATE [Order details]SET Quantity = 600WHERE OrderID = 10272AND ProductID = 20

UPDATE [Order details]SET Discount = 0.4WHERE ProductID = 20

Peter

@Count 830

@Total 1304284.24

@Average 1542.78

@Total / @Count

1571.43

SELECT @Average = AVG(TotalPrice)FROM (…) AS TotOrders

2020

Page 21: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Phantom Reads (1)Phantom Reads (1)

Peter Paul

Mary(SQL Server)

OrderID ProductID

UnitPrice

10259 37 20.8

10337 37 20.8

10408 37 20.8

10523 37 26.0

10847 37 26.0

10966 37 26.0

SELECT OrderID, ProductID, UnitPrice FROM [Order Details]WHERE ProductID = 37

2121

Page 22: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Phantom Reads (2)Phantom Reads (2)

Peter Paul

Mary(SQL Server)

OrderID ProductID

UnitPrice

10259 37 20.80

10337 37 20.80

10408 37 20.80

10523 37 26.00

10847 37 26.00

10966 37 26.00

10615 37 31.54INSERT [Order details] (OrderID, ProductID, UnitPrice, Quantity, Discount)VALUES (10615, 37, 31.54, 20, 0.1)

SELECT OrderID, ProductID, UnitPrice FROM [Order Details]WHERE ProductID = 37

2222

Page 23: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Phantom Reads (3)Phantom Reads (3)

Peter Paul

Mary(SQL Server)

OrderID ProductID

UnitPrice

10259 37 20.80

10337 37 20.80

10408 37 20.80

10523 37 26.00

10847 37 26.00

10966 37 26.00

10615 37 31.54

SELECT OrderID, ProductID, UnitPrice FROM [Order Details]WHERE ProductID = 37

INSERT [Order details] (OrderID, ProductID, UnitPrice, Quantity, Discount)VALUES (10615, 37, 31.54, 20, 0.1)

SELECT OrderID, ProductID, UnitPrice FROM [Order Details]WHERE ProductID = 37

2323

Page 24: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Isolation levelsIsolation levels

• Transact-SQL:– READ COMMITTED– READ UNCOMMITTED– REPEATABLE READ– SERIALIZABLE

• Extra .NET Isolation Levels:– Chaos (not valid for SQLClient)– Unspecified (not settable for SQLClient)

2424

Page 25: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Isolation levels vs. Concurrency Isolation levels vs. Concurrency problemsproblems

P Problem

S Solution

X solved by standard

exclusive locks inside transactions

Isolation Level

Lost Update X X X XDirty Read P S S S

Inconsistent Analysis P P S SPhantom

Reads P P P S

Con

curr

en

cyPro

ble

m

REA

DU

NC

OM

MIT

TED

REA

DC

OM

MIT

TED

REPEA

TA

BLE

REA

D

SER

IALIZ

AB

LE

2525

Page 26: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

READ COMMITTEDREAD COMMITTED

• Default isolation level• Avoids Dirty Reads

– myTran = myconn.BeginTransaction( IsolationLevel.ReadCommitted)

– SELECT … FROM … WITH (READCOMMITTED)– SELECT … FROM … WITH (READPAST)– SET TRANSACTION ISOLATION LEVEL READ COMMITTED

• Requests Shared locks for the duration of the reading operation

• Requests Exclusive locks for each modification

2626

Page 27: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

READ UNCOMMITTEDREAD UNCOMMITTED

• Isolation level only suitable for “sneaking around”– myTran =

myconn.BeginTransaction( IsolationLevel.ReadUnCommitted)

– SELECT … FROM … WITH (READUNCOMMITTED)– SELECT … FROM … WITH (NOLOCK)– SET TRANSACTION ISOLATION LEVEL READ

UNCOMMITTED

• Doesn’t request Shared locks at all• Requests Exclusive locks for each modification

2727

Page 28: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

REPEATABLE READREPEATABLE READ

• Quite a restrictive isolation level• Avoids all concurrency problems, except Phantom

Reads – myTran =

myconn.BeginTransaction( IsolationLevel.RepeatableRead)

– SELECT … FROM … WITH (REPEATABLEREAD)– SET TRANSACTION ISOLATION LEVEL REPEATABLE

READ

• Requests Shared locks for the duration of the transaction

• Requests Exclusive locks for each modification

2828

Page 29: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

SERIALIZABLESERIALIZABLE

• The most restrictive isolation level• Avoids all concurrency problems

– myTran = myconn.BeginTransaction( IsolationLevel.Serializable)

– SELECT … FROM … WITH (SERIALIZABLE)– SELECT … FROM … WITH (HOLDLOCK)– SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

• Requests Shared locks for the duration of the transaction

• Requests Exclusive locks for each modification

2929

Page 30: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

TransactionsTransactions

• Transact-SQL transactions

• Distributed transactions

• .NET Manual transactions

• .NET Automatic transactions

3030

Page 31: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Transact-SQL transactionsTransact-SQL transactions

• Transaction Statements

• Nested transactions

• Transactions and Stored Procedures

• Transactions and Triggers

3131

Page 32: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Managing transactions with Managing transactions with Transact-SQL StatementsTransact-SQL Statements

• BEGIN TRAN

• COMMIT TRAN

• ROLLBACK TRAN

3232

Page 33: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Transaction SavepointsTransaction Savepoints

• SAVE TRAN TranName

• ROLLBACK TRAN TranName

3333

Page 34: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Nested transactionsNested transactions

• @@TRANCOUNT tells you how many transaction levels you are in

• For SQL Server there is only one actual transaction

• Commit happens only when all nested transactions are ended

• Rollback cancels all nested transactions at once

3434

Page 35: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Transactions and Stored Transactions and Stored ProceduresProcedures

• After Rollback execution continues, but you are outside transaction boundaries

• If Rollback happens inside a procedure, the calling process receives error 266, Level 16:– Transaction count after EXECUTE indicates that a

COMMIT or ROLLBACK TRANSACTION statement is missing. Previous count = 1, current count = 0.

• Good idea to use save points and inform the outer process using output parameters

3535

Page 36: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Transactions and TriggersTransactions and Triggers

• After Rollback execution continues inside the trigger, but you are outside transaction boundaries and the process terminates when the trigger does.

• Consider using INSTEAD OF triggers to minimize rollbacks

• Consider using cancelling operations instead of rollbacks

3636

Page 37: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Distributed transactionsDistributed transactions

3737

Page 38: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

.NET Manual transactions.NET Manual transactions

3838

Page 39: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

.NET Automatic transactions.NET Automatic transactions

• Apply the TransactionAttribute to your class.

• Derive your class from the ServicedComponent Class.

• Sign the assembly with a strong name. – To sign the assembly using attributes create a

key pair using the Sn.exe utility. – sn -k MCTCon.snk

3939

Page 40: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

.NET Automatic transactions (2).NET Automatic transactions (2)<Assembly: ApplicationName("MCTCON")>

<Assembly: AssemblyKeyFileAttribute("MCTCON.snk")>

<Transaction(TransactionOption.Required)> Public Class clsProduct

Inherits ServicedComponent

Dim myConnection As SqlConnection

<AutoComplete()> Public Sub RaisePrice(ByVal ProductID As Integer, ByVal amount As Integer)

OpenConnection()

Updateproduct(ProductID, amount)

CloseConnection()

End Sub4040

Page 41: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

.NET Automatic transactions (3).NET Automatic transactions (3)

4141

Page 42: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

LocksLocks

• Dynamic locking strategy

• Locking SQL Server resources

• Types of locks

• Hunting for locks

4242

Page 43: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Dynamic locking strategyDynamic locking strategy

• SQL Server tries to minimize locking costs balancing:– Lock granularity– Lock maintenance cost

• SQL Server 2000 defaults to row lock when necessary

• Uses latches, lightweight synchronization objects, for internal operations, minimizing expensive locks

4343

Page 44: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Locking SQL Server resourcesLocking SQL Server resources

• SQL Server 2000 can lock:– Data Row– Index Key– Any page– Extent– Table– Database

4444

Page 45: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Types of locksTypes of locks

• Shared (S) • Update (U) • Exclusive (X)• Intent:

– intent shared (IS)– intent exclusive (IX)– shared with intent exclusive (SIX)

• Schema:– schema modification (Sch-M)– schema stability (Sch-S).

• Bulk Update (BU)

4545

Page 46: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

A typical case of Deadlock A typical case of Deadlock involving two connections (demo)involving two connections (demo)

4646

Page 47: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

A Deadlock situation involving A Deadlock situation involving more than two connections more than two connections

(demo)(demo)

4747

Page 48: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Binding connections (demo)Binding connections (demo)

4848

Page 49: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Hunting for locksHunting for locks

• Profiler can detect locks

• Performance Monitor counts locks

• Transaction Log registers transactions. It doesn’t register locks

• Convert sp_lock into fn_lockSELECT *

FROM ::fn_lock()

WHERE Status = 'WAIT'

4949

Page 50: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Using Profiler to detect locks Using Profiler to detect locks (demo)(demo)

5050

Page 51: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Using Performance Monitor to Using Performance Monitor to count locks (demo)count locks (demo)

5151

Page 52: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Detecting transactions in the Detecting transactions in the Transaction Log (demo)Transaction Log (demo)

5252

Page 53: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Locking techniques from ADO.NETLocking techniques from ADO.NET

• Optimistic concurrency

• Pessimistic concurrency

• User-defined concurrency

5353

Page 54: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Optimistic concurrencyOptimistic concurrency

• Default behavior from DataAdapter• Based on sp_executesql• SET clause with all new values:

– Updated columns– Unchanged columns

• WHERE clause with all old values:– Updated columns– Unchanged columns

5454

Page 55: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Pessimistic ConcurrencyPessimistic Concurrency

• Implemented through SqlCommand objects and stored procedures

• Not scaleable:– Requires maintaining connection open– Open transaction– Too much locking for too much time

• Necessary in some scenarios

5555

Page 56: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

User-defined concurrencyUser-defined concurrency

• Trace changes on individual columns

• Avoid unnecessary trigger execution

• Avoid unnecessary locks

• Fewer conflicts

• Requires careful design

5656

Page 57: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

User-defined concurrency from User-defined concurrency from ADO.NET (demo)ADO.NET (demo)

5757

Page 58: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Application locksApplication locks

• Give applications access to the SQL Server Lock Manager

• sp_getapplock 'resource_name', 'lock_mode', 'lock_owner', 'lockTimeout‘

• sp_releaseapplock 'resource_name‘, 'lock_owner' ]

5858

Page 59: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Do you want to know more?Do you want to know more?

• “Inside SQL Server 2000” (Kalen Delaney, MSPress)• “Advanced Transact-SQL for SQL Server 2000” (Itzik

Ben-Gan & Tom Moreau, APress)• “SQL Server 2000 Programming” (Robert Vieira, WROX)• “Microsoft SQL Server 2000 Programming by Example”

(Fernando G. Guerrero & Carlos Eduardo Rojas, QUE)• SQL Server 2000 Resource Kit (MSPress & TechNet)• Visit the Microsoft public newsgroups:

– msnews.microsoft.com/microsoft.public.sqlserver.*

• Download the source code of this session from:– http://www.callsql.com/en/articles

6060

Page 60: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Do you want to know even Do you want to know even more?more?

• Visit the Microsoft public newsgroups:– msnews.microsoft.com/

microsoft.public.sqlserver.*– msnews.microsoft.com/

microsoft.public.dotnet.*

6161

Page 61: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Thank you! Thank you! Questions?Questions?

• Download the source code of this session from:– http://www.callsql.com/en/articles

• You can contact me at:– [email protected]

Page 62: Vda305 concurrency guerrero

VS .NET ConnectionsVS .NET Connections

Thank you!Thank you!

• Please drop off your session evaluations in the basket at the back of the room!

• Your comments are greatly appreciated!